use serde::{Deserialize, Serialize};

/// Normalized blockchain currency identifier.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Currency {
    BTC,
    ETH,
    SOL,
    USDT,
    XRP,
    LTC,
    /// Fallback for currencies not in the known set.
    #[serde(untagged)]
    Other(String),
}

impl Currency {
    /// Normalize a raw currency/blockchain string into a canonical variant.
    /// Covers all 18 currency types in the OFAC SDN list plus common aliases.
    pub fn normalize(raw: &str) -> Self {
        match raw.to_lowercase().as_str() {
            "btc" | "bch" | "xbt" | "bitcoin" | "btg" | "bsv" => Self::BTC,
            "eth" | "ethereum" | "etc" | "arb" | "bsc" => Self::ETH,
            "sol" | "solana" => Self::SOL,
            "usdt" => Self::USDT,
            "usdc" => Self::Other("USDC".into()),
            "xrp" | "ripple" => Self::XRP,
            "ltc" | "litecoin" => Self::LTC,
            "trx" | "tron" => Self::Other("TRX".into()),
            "xmr" | "monero" => Self::Other("XMR".into()),
            "dash" => Self::Other("DASH".into()),
            "zec" | "zcash" => Self::Other("ZEC".into()),
            "xvg" | "verge" => Self::Other("XVG".into()),
            _ => Self::Other(raw.to_uppercase()),
        }
    }

    pub fn as_str(&self) -> &str {
        match self {
            Self::BTC => "BTC",
            Self::ETH => "ETH",
            Self::SOL => "SOL",
            Self::USDT => "USDT",
            Self::XRP => "XRP",
            Self::LTC => "LTC",
            Self::Other(s) => s.as_str(),
        }
    }
}

impl std::fmt::Display for Currency {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

/// The source/list that flagged an address.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SourceType {
    OFAC,
    RANSOM,
    #[serde(untagged)]
    Other(String),
}

impl SourceType {
    pub fn as_str(&self) -> &str {
        match self {
            Self::OFAC => "OFAC",
            Self::RANSOM => "RANSOM",
            Self::Other(s) => s.as_str(),
        }
    }
}

impl std::fmt::Display for SourceType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

/// A sanctioned blockchain address stored in the database.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SanctionedAddress {
    pub currency: String,
    pub address: String,
    #[serde(rename = "type")]
    pub source_type: String,
}

/// Result of checking a single address against the sanctions database.
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AddressCheckResult {
    pub address: String,
    pub on_list: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub list_name: Option<String>,
    pub confidence: u8,
}

impl AddressCheckResult {
    pub fn found(address: String, list_name: String) -> Self {
        Self {
            address,
            on_list: true,
            list_name: Some(list_name),
            confidence: 100,
        }
    }

    pub fn not_found(address: String) -> Self {
        Self {
            address,
            on_list: false,
            list_name: None,
            confidence: 0,
        }
    }
}

/// A sanctioned country/jurisdiction from OFAC programs.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SanctionedCountry {
    pub code: String,
    pub name: String,
    pub program: String,
}

/// Public sanctions list response — flat lists of addresses and country codes.
///
/// This is the format consumed by cedros-pay and other clients via `GET /v1/lists`.
/// Addresses are a flat list of all sanctioned wallet addresses across all blockchains.
/// Countries are ISO 3166-1 alpha-2 codes of sanctioned jurisdictions.
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SanctionsListResponse {
    pub addresses: Vec<String>,
    pub countries: Vec<String>,
}

/// An API key record from the database.
#[derive(Debug, Clone)]
pub struct ApiKey {
    pub key: String,
    pub active: bool,
    pub user_id: String,
}

/// Parsed entry from the ransomware tracker API.
#[derive(Debug, Deserialize)]
pub(crate) struct RansomwareExport {
    pub result: Vec<RansomwareEntry>,
}

#[derive(Debug, Deserialize)]
pub(crate) struct RansomwareEntry {
    pub address: String,
    pub blockchain: String,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn normalize_known_currencies() {
        // BTC family
        assert_eq!(Currency::normalize("bitcoin"), Currency::BTC);
        assert_eq!(Currency::normalize("BCH"), Currency::BTC);
        assert_eq!(Currency::normalize("XBT"), Currency::BTC);
        assert_eq!(Currency::normalize("BTG"), Currency::BTC);
        assert_eq!(Currency::normalize("BSV"), Currency::BTC);
        // ETH family (EVM chains grouped under ETH)
        assert_eq!(Currency::normalize("ethereum"), Currency::ETH);
        assert_eq!(Currency::normalize("ETC"), Currency::ETH);
        assert_eq!(Currency::normalize("ARB"), Currency::ETH);
        assert_eq!(Currency::normalize("BSC"), Currency::ETH);
        // Others
        assert_eq!(Currency::normalize("solana"), Currency::SOL);
        assert_eq!(Currency::normalize("ripple"), Currency::XRP);
        assert_eq!(Currency::normalize("litecoin"), Currency::LTC);
        assert_eq!(Currency::normalize("usdt"), Currency::USDT);
        // New OFAC currency types
        assert_eq!(Currency::normalize("TRX"), Currency::Other("TRX".into()));
        assert_eq!(Currency::normalize("XMR"), Currency::Other("XMR".into()));
        assert_eq!(Currency::normalize("DASH"), Currency::Other("DASH".into()));
        assert_eq!(Currency::normalize("ZEC"), Currency::Other("ZEC".into()));
        assert_eq!(Currency::normalize("USDC"), Currency::Other("USDC".into()));
        assert_eq!(Currency::normalize("XVG"), Currency::Other("XVG".into()));
    }

    #[test]
    fn normalize_unknown_currency_uppercases() {
        assert_eq!(Currency::normalize("doge"), Currency::Other("DOGE".into()));
    }

    #[test]
    fn address_check_result_found_shape() {
        let result = AddressCheckResult::found("abc".into(), "OFAC".into());
        assert!(result.on_list);
        assert_eq!(result.confidence, 100);
        assert_eq!(result.list_name.as_deref(), Some("OFAC"));
    }

    #[test]
    fn address_check_result_not_found_shape() {
        let result = AddressCheckResult::not_found("abc".into());
        assert!(!result.on_list);
        assert_eq!(result.confidence, 0);
        assert!(result.list_name.is_none());
    }
}
