use crate::{Balance, BlockNumber, RuntimeOrigin, CSPR, DAYS, HOURS, MINUTES};
use pallet_ranked_collective::Rank;
use sp_runtime::{traits::Convert, Perbill};

pub type TrackId = u16;

pub mod constants {
    use super::TrackId;

    pub const GENIUSES: TrackId = 1;
    pub const DEGENS: TrackId = 2;
    pub const ZOMBIES: TrackId = 3;
    pub const SKELETONS: TrackId = 4;
    pub const GHOSTS: TrackId = 5;

    pub const RETAIN_AT_GENIUSES: TrackId = 10;
    pub const RETAIN_AT_DEGENS: TrackId = 11;
    pub const RETAIN_AT_ZOMBIES: TrackId = 12;
    pub const RETAIN_AT_SKELETONS: TrackId = 13;
    pub const RETAIN_AT_GHOSTS: TrackId = 14;

    pub const PROMOTE_TO_GENIUSES: TrackId = 20;
    pub const PROMOTE_TO_DEGENS: TrackId = 21;
    pub const PROMOTE_TO_ZOMBIES: TrackId = 22;
    pub const PROMOTE_TO_SKELETONS: TrackId = 23;
    pub const PROMOTE_TO_GHOSTS: TrackId = 24;
}

/// Convert the track ID (defined above) into the minimum rank required.
pub struct MinRankOfClass;
impl Convert<TrackId, Rank> for MinRankOfClass {
    fn convert(a: TrackId) -> Rank {
        match a {
            regular @ 1..=5 => regular,
            retention @ 10..=13 => retention - 8,
            promotion @ 20..=23 => promotion - 18,
            14 | 24 => 5,
            _ => Rank::MAX,
        }
    }
}

const RETAIN_MAX_DECIDING: u32 = 25;
const RETAIN_DECISION_DEPOSIT: Balance = 5 * CSPR;
const RETAIN_PREPARE_PERIOD: BlockNumber = 0;
const RETAIN_DECISION_PERIOD: BlockNumber = 14 * DAYS;
const RETAIN_CONFIRM_PERIOD: BlockNumber = HOURS;
const RETAIN_MIN_ENACTMENT_PERIOD: BlockNumber = 0;
const RETAIN_MIN_APPROVAL: pallet_referenda::Curve = pallet_referenda::Curve::LinearDecreasing {
    length: Perbill::from_percent(100),
    floor: Perbill::from_percent(60),
    ceil: Perbill::from_percent(100),
};
const RETAIN_MIN_SUPPORT: pallet_referenda::Curve = pallet_referenda::Curve::LinearDecreasing {
    length: Perbill::from_percent(100),
    floor: Perbill::from_percent(10),
    ceil: Perbill::from_percent(100),
};

const PROMOTE_MAX_DECIDING: u32 = 10;
const PROMOTE_DECISION_DEPOSIT: Balance = 5 * CSPR;
const PROMOTE_PREPARE_PERIOD: BlockNumber = 0;
const PROMOTE_DECISION_PERIOD: BlockNumber = 30 * DAYS;
const PROMOTE_CONFIRM_PERIOD: BlockNumber = HOURS;
const PROMOTE_MIN_ENACTMENT_PERIOD: BlockNumber = 0;
const PROMOTE_MIN_APPROVAL: pallet_referenda::Curve = pallet_referenda::Curve::LinearDecreasing {
    length: Perbill::from_percent(100),
    floor: Perbill::from_percent(60),
    ceil: Perbill::from_percent(100),
};
const PROMOTE_MIN_SUPPORT: pallet_referenda::Curve = pallet_referenda::Curve::LinearDecreasing {
    length: Perbill::from_percent(100),
    floor: Perbill::from_percent(10),
    ceil: Perbill::from_percent(100),
};

const TRACKS_DATA: [(u16, pallet_referenda::TrackInfo<Balance, BlockNumber>); 15] = [
    (
        constants::GENIUSES,
        pallet_referenda::TrackInfo {
            name: "geniuses",
            max_deciding: 10,
            decision_deposit: 5 * CSPR,
            prepare_period: 30 * MINUTES,
            decision_period: 7 * DAYS,
            confirm_period: 30 * MINUTES,
            min_enactment_period: 5 * MINUTES,
            min_approval: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(50),
                ceil: Perbill::from_percent(100),
            },
            min_support: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(0),
                ceil: Perbill::from_percent(100),
            },
        },
    ),
    (
        constants::DEGENS,
        pallet_referenda::TrackInfo {
            name: "degens",
            max_deciding: 10,
            decision_deposit: 5 * CSPR,
            prepare_period: 30 * MINUTES,
            decision_period: 7 * DAYS,
            confirm_period: 30 * MINUTES,
            min_enactment_period: 5 * MINUTES,
            min_approval: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(50),
                ceil: Perbill::from_percent(100),
            },
            min_support: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(0),
                ceil: Perbill::from_percent(100),
            },
        },
    ),
    (
        constants::ZOMBIES,
        pallet_referenda::TrackInfo {
            name: "zombies",
            max_deciding: 10,
            decision_deposit: 5 * CSPR,
            prepare_period: 30 * MINUTES,
            decision_period: 7 * DAYS,
            confirm_period: 30 * MINUTES,
            min_enactment_period: 5 * MINUTES,
            min_approval: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(50),
                ceil: Perbill::from_percent(100),
            },
            min_support: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(0),
                ceil: Perbill::from_percent(100),
            },
        },
    ),
    (
        constants::SKELETONS,
        pallet_referenda::TrackInfo {
            name: "skeletons",
            max_deciding: 10,
            decision_deposit: 5 * CSPR,
            prepare_period: 30 * MINUTES,
            decision_period: 7 * DAYS,
            confirm_period: 30 * MINUTES,
            min_enactment_period: 5 * MINUTES,
            min_approval: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(50),
                ceil: Perbill::from_percent(100),
            },
            min_support: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(0),
                ceil: Perbill::from_percent(100),
            },
        },
    ),
    (
        constants::GHOSTS,
        pallet_referenda::TrackInfo {
            name: "skeletons",
            max_deciding: 10,
            decision_deposit: 5 * CSPR,
            prepare_period: 30 * MINUTES,
            decision_period: 7 * DAYS,
            confirm_period: 30 * MINUTES,
            min_enactment_period: 5 * MINUTES,
            min_approval: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(50),
                ceil: Perbill::from_percent(100),
            },
            min_support: pallet_referenda::Curve::LinearDecreasing {
                length: Perbill::from_percent(100),
                floor: Perbill::from_percent(0),
                ceil: Perbill::from_percent(100),
            },
        },
    ),
    (
        constants::RETAIN_AT_GENIUSES,
        pallet_referenda::TrackInfo {
            name: "retain a genius",
            max_deciding: RETAIN_MAX_DECIDING,
            decision_deposit: RETAIN_DECISION_DEPOSIT,
            prepare_period: RETAIN_PREPARE_PERIOD,
            decision_period: RETAIN_DECISION_PERIOD,
            confirm_period: RETAIN_CONFIRM_PERIOD,
            min_enactment_period: RETAIN_MIN_ENACTMENT_PERIOD,
            min_approval: RETAIN_MIN_APPROVAL,
            min_support: RETAIN_MIN_SUPPORT,
        },
    ),
    (
        constants::RETAIN_AT_DEGENS,
        pallet_referenda::TrackInfo {
            name: "retain a degen",
            max_deciding: RETAIN_MAX_DECIDING,
            decision_deposit: RETAIN_DECISION_DEPOSIT,
            prepare_period: RETAIN_PREPARE_PERIOD,
            decision_period: RETAIN_DECISION_PERIOD,
            confirm_period: RETAIN_CONFIRM_PERIOD,
            min_enactment_period: RETAIN_MIN_ENACTMENT_PERIOD,
            min_approval: RETAIN_MIN_APPROVAL,
            min_support: RETAIN_MIN_SUPPORT,
        },
    ),
    (
        constants::RETAIN_AT_ZOMBIES,
        pallet_referenda::TrackInfo {
            name: "retain a zombie",
            max_deciding: RETAIN_MAX_DECIDING,
            decision_deposit: RETAIN_DECISION_DEPOSIT,
            prepare_period: RETAIN_PREPARE_PERIOD,
            decision_period: RETAIN_DECISION_PERIOD,
            confirm_period: RETAIN_CONFIRM_PERIOD,
            min_enactment_period: RETAIN_MIN_ENACTMENT_PERIOD,
            min_approval: RETAIN_MIN_APPROVAL,
            min_support: RETAIN_MIN_SUPPORT,
        },
    ),
    (
        constants::RETAIN_AT_SKELETONS,
        pallet_referenda::TrackInfo {
            name: "retain a skeleton",
            max_deciding: RETAIN_MAX_DECIDING,
            decision_deposit: RETAIN_DECISION_DEPOSIT,
            prepare_period: RETAIN_PREPARE_PERIOD,
            decision_period: RETAIN_DECISION_PERIOD,
            confirm_period: RETAIN_CONFIRM_PERIOD,
            min_enactment_period: RETAIN_MIN_ENACTMENT_PERIOD,
            min_approval: RETAIN_MIN_APPROVAL,
            min_support: RETAIN_MIN_SUPPORT,
        },
    ),
    (
        constants::RETAIN_AT_GHOSTS,
        pallet_referenda::TrackInfo {
            name: "retain a ghost",
            max_deciding: RETAIN_MAX_DECIDING,
            decision_deposit: RETAIN_DECISION_DEPOSIT,
            prepare_period: RETAIN_PREPARE_PERIOD,
            decision_period: RETAIN_DECISION_PERIOD,
            confirm_period: RETAIN_CONFIRM_PERIOD,
            min_enactment_period: RETAIN_MIN_ENACTMENT_PERIOD,
            min_approval: RETAIN_MIN_APPROVAL,
            min_support: RETAIN_MIN_SUPPORT,
        },
    ),
    (
        constants::PROMOTE_TO_GENIUSES,
        pallet_referenda::TrackInfo {
            name: "promote to genius",
            max_deciding: PROMOTE_MAX_DECIDING,
            decision_deposit: PROMOTE_DECISION_DEPOSIT,
            prepare_period: PROMOTE_PREPARE_PERIOD,
            decision_period: PROMOTE_DECISION_PERIOD,
            confirm_period: PROMOTE_CONFIRM_PERIOD,
            min_enactment_period: PROMOTE_MIN_ENACTMENT_PERIOD,
            min_approval: PROMOTE_MIN_APPROVAL,
            min_support: PROMOTE_MIN_SUPPORT,
        },
    ),
    (
        constants::PROMOTE_TO_DEGENS,
        pallet_referenda::TrackInfo {
            name: "promote to degen",
            max_deciding: PROMOTE_MAX_DECIDING,
            decision_deposit: PROMOTE_DECISION_DEPOSIT,
            prepare_period: PROMOTE_PREPARE_PERIOD,
            decision_period: PROMOTE_DECISION_PERIOD,
            confirm_period: PROMOTE_CONFIRM_PERIOD,
            min_enactment_period: PROMOTE_MIN_ENACTMENT_PERIOD,
            min_approval: PROMOTE_MIN_APPROVAL,
            min_support: PROMOTE_MIN_SUPPORT,
        },
    ),
    (
        constants::PROMOTE_TO_ZOMBIES,
        pallet_referenda::TrackInfo {
            name: "promote to zombie",
            max_deciding: PROMOTE_MAX_DECIDING,
            decision_deposit: PROMOTE_DECISION_DEPOSIT,
            prepare_period: PROMOTE_PREPARE_PERIOD,
            decision_period: PROMOTE_DECISION_PERIOD,
            confirm_period: PROMOTE_CONFIRM_PERIOD,
            min_enactment_period: PROMOTE_MIN_ENACTMENT_PERIOD,
            min_approval: PROMOTE_MIN_APPROVAL,
            min_support: PROMOTE_MIN_SUPPORT,
        },
    ),
    (
        constants::PROMOTE_TO_SKELETONS,
        pallet_referenda::TrackInfo {
            name: "promote to skeleton",
            max_deciding: PROMOTE_MAX_DECIDING,
            decision_deposit: PROMOTE_DECISION_DEPOSIT,
            prepare_period: PROMOTE_PREPARE_PERIOD,
            decision_period: PROMOTE_DECISION_PERIOD,
            confirm_period: PROMOTE_CONFIRM_PERIOD,
            min_enactment_period: PROMOTE_MIN_ENACTMENT_PERIOD,
            min_approval: PROMOTE_MIN_APPROVAL,
            min_support: PROMOTE_MIN_SUPPORT,
        },
    ),
    (
        constants::PROMOTE_TO_GHOSTS,
        pallet_referenda::TrackInfo {
            name: "promote to ghost",
            max_deciding: PROMOTE_MAX_DECIDING,
            decision_deposit: PROMOTE_DECISION_DEPOSIT,
            prepare_period: PROMOTE_PREPARE_PERIOD,
            decision_period: PROMOTE_DECISION_PERIOD,
            confirm_period: PROMOTE_CONFIRM_PERIOD,
            min_enactment_period: PROMOTE_MIN_ENACTMENT_PERIOD,
            min_approval: PROMOTE_MIN_APPROVAL,
            min_support: PROMOTE_MIN_SUPPORT,
        },
    ),
];

pub struct TracksInfo;
impl pallet_referenda::TracksInfo<Balance, BlockNumber> for TracksInfo {
    type Id = TrackId;
    type RuntimeOrigin = <RuntimeOrigin as frame_support::traits::OriginTrait>::PalletsOrigin;

    fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo<Balance, BlockNumber>)] {
        &TRACKS_DATA[..]
    }

    fn track_for(id: &Self::RuntimeOrigin) -> Result<Self::Id, ()> {
        use super::origins::Origin;
        use constants as tracks;

        #[cfg(feature = "runtime-benchmarks")]
        {
            // For benchmark we enable root origin.
            // It is important that this is NOT availiable in production!
            let root: Self::RuntimeOrigin = frame_system::RawOrigin::Root.into();
            if &root == id {
                return Ok(tracks::GHOSTS);
            }
        }

        match Origin::try_from(id.clone()) {
            Ok(Origin::Geniuses) => Ok(tracks::GENIUSES),
            Ok(Origin::Degens) => Ok(tracks::DEGENS),
            Ok(Origin::Zombies) => Ok(tracks::ZOMBIES),
            Ok(Origin::Skeletons) => Ok(tracks::SKELETONS),
            Ok(Origin::Ghosts) => Ok(tracks::GHOSTS),

            Ok(Origin::RetainAt1Level) => Ok(tracks::RETAIN_AT_GENIUSES),
            Ok(Origin::RetainAt2Level) => Ok(tracks::RETAIN_AT_DEGENS),
            Ok(Origin::RetainAt3Level) => Ok(tracks::RETAIN_AT_ZOMBIES),
            Ok(Origin::RetainAt4Level) => Ok(tracks::RETAIN_AT_SKELETONS),
            Ok(Origin::RetainAt5Level) => Ok(tracks::RETAIN_AT_GHOSTS),

            Ok(Origin::PromoteTo1Level) => Ok(tracks::PROMOTE_TO_GENIUSES),
            Ok(Origin::PromoteTo2Level) => Ok(tracks::PROMOTE_TO_GENIUSES),
            Ok(Origin::PromoteTo3Level) => Ok(tracks::PROMOTE_TO_ZOMBIES),
            Ok(Origin::PromoteTo4Level) => Ok(tracks::PROMOTE_TO_SKELETONS),
            Ok(Origin::PromoteTo5Level) => Ok(tracks::PROMOTE_TO_GHOSTS),

            _ => Err(()),
        }
    }
}
pallet_referenda::impl_tracksinfo_get!(TracksInfo, Balance, BlockNumber);
