use crate::fetch::fetch_token_security;
use crate::hard_stops::check_hard_stops;
use crate::platform::{PlatformProfile, detect_platform};
use crate::rugcheck::{RugcheckReport, fetch_rugcheck_report, score_liquidity};
use anyhow::Result;
use birdeye_client::client::BirdeyeClient;
use birdeye_client::defi::types::BirdeyeTokenSecurity;
use serde::Serialize;

/// Security scoring result with detailed breakdown
#[derive(Debug, Clone, Serialize)]
pub struct Score {
    /// Overall security score (0-100)
    pub score: f64,

    /// Whether any hard stops were triggered
    pub hard_stop_triggered: bool,

    /// Hard stop violations (if any)
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub hard_stop_violations: Vec<HardStopInfo>,

    /// Detected launch platform
    pub launch_platform: String,
    pub platform_trust_level: String,

    /// Per-category score breakdown
    pub score_breakdown: CategoryBreakdown,

    /// Risk level classification
    pub risk_level: RiskLevel,

    /// Detailed findings and warnings
    pub warnings: Vec<String>,
    pub positive_signals: Vec<String>,

    /// Raw data used for scoring
    pub details: ScoreDetails,

    /// Per-category scoring reasons (text output only, not serialized to JSON)
    #[serde(skip)]
    pub authority_reasons: Vec<String>,
    #[serde(skip)]
    pub holder_reasons: Vec<String>,
    #[serde(skip)]
    pub metadata_reasons: Vec<String>,
    #[serde(skip)]
    pub trust_reasons: Vec<String>,
    #[serde(skip)]
    pub liquidity_reasons: Vec<String>,
}

#[derive(Debug, Clone, Serialize)]
pub struct CategoryBreakdown {
    pub authority: f64,           // 0-25
    pub holder_distribution: f64, // 0-20
    pub metadata: f64,            // 0-15
    pub trust: f64,               // 0-15
    pub liquidity: f64,           // 0-25 (rugcheck-based; 25 when no rugcheck data)
}

#[derive(Debug, Clone, Serialize)]
pub struct HardStopInfo {
    pub severity: String,
    pub category: String,
    pub message: String,
    pub description: String,
}

#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum RiskLevel {
    Critical,  // 0-39:   Very High Risk / Likely Scam
    Poor,      // 40-59:  High Risk
    Moderate,  // 60-74:  Exercise Caution
    Good,      // 75-89:  Generally Safe
    Excellent, // 90-100: Very Safe
}

/// UI styling metadata for a [`RiskLevel`] classification.
#[derive(Debug, Clone, Serialize)]
pub struct RiskLevelStyle {
    pub color: &'static str,
    pub hex: &'static str,
    pub tailwind_text: &'static str,
    pub tailwind_bg: &'static str,
    pub tailwind_border: &'static str,
}

impl RiskLevel {
    /// Returns the display label for this risk level.
    pub fn label(&self) -> &'static str {
        match self {
            RiskLevel::Critical => "Critical",
            RiskLevel::Poor => "Poor",
            RiskLevel::Moderate => "Moderate",
            RiskLevel::Good => "Good",
            RiskLevel::Excellent => "Excellent",
        }
    }

    /// Returns the UI styling metadata (color, hex, Tailwind classes) for this risk level.
    pub fn style(&self) -> RiskLevelStyle {
        match self {
            RiskLevel::Critical => RiskLevelStyle {
                color: "Red",
                hex: "#EF4444",
                tailwind_text: "text-red-500",
                tailwind_bg: "bg-red-500/10",
                tailwind_border: "border-red-500/30",
            },
            RiskLevel::Poor => RiskLevelStyle {
                color: "Orange",
                hex: "#F97316",
                tailwind_text: "text-orange-500",
                tailwind_bg: "bg-orange-500/10",
                tailwind_border: "border-orange-500/30",
            },
            RiskLevel::Moderate => RiskLevelStyle {
                color: "Yellow",
                hex: "#EAB308",
                tailwind_text: "text-yellow-500",
                tailwind_bg: "bg-yellow-500/10",
                tailwind_border: "border-yellow-500/30",
            },
            RiskLevel::Good => RiskLevelStyle {
                color: "Green",
                hex: "#22C55E",
                tailwind_text: "text-green-500",
                tailwind_bg: "bg-green-500/10",
                tailwind_border: "border-green-500/30",
            },
            RiskLevel::Excellent => RiskLevelStyle {
                color: "Blue",
                hex: "#3B82F6",
                tailwind_text: "text-blue-500",
                tailwind_bg: "bg-blue-500/10",
                tailwind_border: "border-blue-500/30",
            },
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct ScoreDetails {
    pub mint_authority_revoked: bool,
    pub freeze_authority_revoked: bool,
    pub mutable_metadata: bool,
    pub top10_holder_percent: f64,
    pub creator_percentage: f64,
    pub is_token2022: bool,
    pub has_transfer_fee: bool,
    pub non_transferable: bool,
    pub jup_strict_list: bool,
    pub is_fake_token: bool,
    /// Detected launch platform name
    pub launch_platform: String,
    /// Trust level of the detected platform
    pub platform_trust_level: String,
    /// Percentage of LP tokens locked (from rugcheck.xyz); None if not fetched
    pub lp_locked_pct: Option<f64>,
    /// Rugcheck raw risk score; None if not fetched
    pub rugcheck_score: Option<u64>,
}

/// Fetch security data and compute a score for a Solana mint.
///
/// Pass `rugcheck_client: Some(&client)` to include real LP lock and risk data
/// from rugcheck.xyz (25-point liquidity category). When `None`, the liquidity
/// category is awarded its full 25 points without fetching rugcheck.
///
/// Rugcheck failures are non-fatal: a warning is printed and scoring falls back
/// to the 25-pt grant.
///
/// # Errors
/// Returns an error only if the Birdeye API call fails.
pub async fn security_score(
    birdeye_client: &BirdeyeClient,
    rugcheck_client: Option<&reqwest::Client>,
    mint: &str,
) -> Result<Score> {
    let (security, rugcheck) = match rugcheck_client {
        None => (fetch_token_security(birdeye_client, mint).await?, None),
        Some(client) => {
            let (security_result, rugcheck_result) = tokio::join!(
                fetch_token_security(birdeye_client, mint),
                fetch_rugcheck_report(client, mint)
            );
            let rugcheck = match rugcheck_result {
                Ok(r) => Some(r),
                Err(e) => {
                    eprintln!(
                        "⚠ rugcheck.xyz fetch failed (liquidity score set to 25): {}",
                        e
                    );
                    None
                }
            };
            (security_result?, rugcheck)
        }
    };
    Ok(score(mint, &security, rugcheck.as_ref()))
}

/// Calculate a security score from already-fetched token security data.
///
/// Pass `rugcheck = Some(report)` to score the exit liquidity category using
/// real LP lock data. When `None`, the full 25 pts are granted (backward-compatible).
///
/// Platform detection runs first: known launchpads (Pump.fun, etc.) get
/// adjusted scoring rules that avoid false positives from bonding curve mechanics.
///
/// Score is out of 100: Authority (25) + Holder Distribution (20) +
/// Metadata (15) + Trust (15) + Exit Liquidity (25).
pub fn score(
    mint: &str,
    security: &BirdeyeTokenSecurity,
    rugcheck: Option<&RugcheckReport>,
) -> Score {
    let mut warnings = Vec::new();
    let mut positive_signals = Vec::new();

    // === PLATFORM DETECTION ===
    let profile = detect_platform(mint, security);

    for note in &profile.notes {
        positive_signals.push(note.to_string());
    }

    // === HARD STOPS ===
    let hard_stop_result = check_hard_stops(security, &profile, rugcheck);

    let hard_stop_violations: Vec<HardStopInfo> = hard_stop_result
        .violations
        .iter()
        .map(|v| HardStopInfo {
            severity: format!("{:?}", v.severity),
            category: v.category.clone(),
            message: v.message.clone(),
            description: v.description.clone(),
        })
        .collect();

    for violation in &hard_stop_result.violations {
        warnings.push(violation.message.clone());
    }

    // === CATEGORY 1: AUTHORITY CONTROLS (0-25 points) ===
    let mut authority_reasons = Vec::new();
    let authority_score = score_authority_controls(
        security,
        &mut warnings,
        &mut positive_signals,
        &mut authority_reasons,
    );

    // === CATEGORY 2: HOLDER DISTRIBUTION (0-20 points) ===
    let mut holder_reasons = Vec::new();
    let holder_score = score_holder_distribution(
        security,
        &profile,
        &mut warnings,
        &mut positive_signals,
        &mut holder_reasons,
    );

    // === CATEGORY 3: METADATA & TOKEN FLAGS (0-15 points) ===
    let mut metadata_reasons = Vec::new();
    let metadata_score = score_metadata_flags(
        security,
        &mut warnings,
        &mut positive_signals,
        &mut metadata_reasons,
    );

    // === CATEGORY 4: TRUST SIGNALS (0-15 points) ===
    let mut trust_reasons = Vec::new();
    let trust_score = score_trust_signals(
        security,
        &mut warnings,
        &mut positive_signals,
        &mut trust_reasons,
    );

    // === CATEGORY 5: LIQUIDITY (0-25 points) ===
    let mut liquidity_reasons = Vec::new();
    let liquidity_score = match rugcheck {
        Some(report) => {
            let result = score_liquidity(report);
            liquidity_reasons.extend(result.reasons);
            warnings.extend(result.warnings);
            positive_signals.extend(result.positive_signals);
            result.score
        }
        None => {
            liquidity_reasons
                .push("+25 — Liquidity not evaluated (rugcheck data unavailable)".to_string());
            25.0
        }
    };

    let calculated_score =
        authority_score + holder_score + metadata_score + trust_score + liquidity_score;

    // Apply hard stop override if present
    let overall_score = if let Some(override_score) = hard_stop_result.override_score {
        calculated_score.min(override_score)
    } else {
        calculated_score
    };

    let risk_level = classify_risk_level(overall_score);

    let details = ScoreDetails {
        mint_authority_revoked: check_mint_authority_revoked(security),
        freeze_authority_revoked: check_freeze_authority_revoked(security),
        mutable_metadata: security.mutable_metadata.unwrap_or(true),
        top10_holder_percent: security.top10_holder_percent.unwrap_or(100.0),
        creator_percentage: security.creator_percentage.unwrap_or(0.0),
        is_token2022: security.is_token2022.unwrap_or(false),
        has_transfer_fee: security.transfer_fee_enable.unwrap_or(false),
        non_transferable: security.non_transferable.unwrap_or(false),
        jup_strict_list: security.jup_strict_list.unwrap_or(false),
        is_fake_token: security.fake_token.unwrap_or(false),
        launch_platform: profile.platform.display_name().to_string(),
        platform_trust_level: profile.trust_level.display_name().to_string(),
        lp_locked_pct: rugcheck.and_then(|r| r.lp_locked_pct),
        rugcheck_score: rugcheck.and_then(|r| r.score),
    };

    Score {
        score: overall_score,
        hard_stop_triggered: hard_stop_result.failed,
        hard_stop_violations,
        launch_platform: profile.platform.display_name().to_string(),
        platform_trust_level: profile.trust_level.display_name().to_string(),
        score_breakdown: CategoryBreakdown {
            authority: authority_score,
            holder_distribution: holder_score,
            metadata: metadata_score,
            trust: trust_score,
            liquidity: liquidity_score,
        },
        risk_level,
        warnings,
        positive_signals,
        details,
        authority_reasons,
        holder_reasons,
        metadata_reasons,
        trust_reasons,
        liquidity_reasons,
    }
}

fn score_authority_controls(
    security: &BirdeyeTokenSecurity,
    warnings: &mut Vec<String>,
    positive_signals: &mut Vec<String>,
    reasons: &mut Vec<String>,
) -> f64 {
    let mut score = 0.0;

    let mint_authority_revoked = check_mint_authority_revoked(security);
    if mint_authority_revoked {
        score += 15.0;
        reasons.push("+15 — Mint authority revoked".to_string());
        positive_signals.push("✓ Mint authority revoked (no inflation risk)".to_string());
    } else {
        reasons.push(
            "+0  — Mint authority NOT revoked (creator can mint unlimited tokens)".to_string(),
        );
        warnings.push(
            "⚠ CRITICAL: Mint authority NOT revoked - creator can mint unlimited tokens"
                .to_string(),
        );
    }

    let freeze_authority_revoked = check_freeze_authority_revoked(security);
    if freeze_authority_revoked {
        score += 10.0;
        reasons.push("+10 — Freeze authority revoked".to_string());
        positive_signals.push("✓ Freeze authority revoked (no account freeze risk)".to_string());
    } else {
        reasons.push("+0  — Freeze authority NOT revoked (accounts can be frozen)".to_string());
        warnings.push("⚠ WARNING: Freeze authority active - accounts can be frozen".to_string());
    }

    if security.non_transferable.unwrap_or(false) {
        reasons.push("→ Score forced to 0: non-transferable flag set".to_string());
        warnings.push("⚠ CRITICAL: Token is non-transferable - cannot be traded!".to_string());
        return 0.0;
    }

    score
}

fn score_holder_distribution(
    security: &BirdeyeTokenSecurity,
    profile: &PlatformProfile,
    warnings: &mut Vec<String>,
    positive_signals: &mut Vec<String>,
    reasons: &mut Vec<String>,
) -> f64 {
    if let Some(override_score) = profile.holder_score_override {
        reasons.push(format!(
            "{:.0}/20 — {} bonding curve: neutral score (protocol holds unsold supply)",
            override_score,
            profile.platform.display_name(),
        ));
        positive_signals.push(format!(
            "ℹ {} holder distribution score: {:.0}/20 (bonding curve supply)",
            profile.platform.display_name(),
            override_score,
        ));
        return override_score;
    }

    let mut score = 0.0;

    let top10_percent = security.top10_holder_percent.unwrap_or(100.0);
    if top10_percent < 20.0 {
        score += 12.0;
        reasons.push(format!(
            "+12 — Top 10 holders own {:.1}% (excellent distribution)",
            top10_percent
        ));
        positive_signals.push(format!(
            "✓ Excellent distribution: Top 10 holders own {:.1}%",
            top10_percent
        ));
    } else if top10_percent < 30.0 {
        score += 10.0;
        reasons.push(format!(
            "+10 — Top 10 holders own {:.1}% (good distribution)",
            top10_percent
        ));
        positive_signals.push(format!(
            "✓ Good distribution: Top 10 holders own {:.1}%",
            top10_percent
        ));
    } else if top10_percent < 50.0 {
        score += 6.0;
        reasons.push(format!(
            "+6  — Top 10 holders own {:.1}% (moderate concentration)",
            top10_percent
        ));
        warnings.push(format!(
            "⚠ Moderate concentration: Top 10 holders own {:.1}%",
            top10_percent
        ));
    } else if top10_percent < 70.0 {
        score += 3.0;
        reasons.push(format!(
            "+3  — Top 10 holders own {:.1}% (high concentration)",
            top10_percent
        ));
        warnings.push(format!(
            "⚠ High concentration: Top 10 holders own {:.1}% - dump risk",
            top10_percent
        ));
    } else {
        reasons.push(format!(
            "+0  — Top 10 holders own {:.1}% (extreme concentration)",
            top10_percent
        ));
        warnings.push(format!(
            "⚠ CRITICAL: Extreme concentration - Top 10 holders own {:.1}%",
            top10_percent
        ));
    }

    let creator_percent = security.creator_percentage.unwrap_or(0.0);
    if creator_percent < 1.0 {
        score += 8.0;
        reasons.push(format!(
            "+8  — Creator holds {:.2}% (minimal)",
            creator_percent
        ));
        positive_signals.push(format!(
            "✓ Creator holds minimal supply ({:.2}%)",
            creator_percent
        ));
    } else if creator_percent < 5.0 {
        score += 6.0;
        reasons.push(format!("+6  — Creator holds {:.2}% (low)", creator_percent));
        positive_signals.push(format!("✓ Low creator holdings ({:.2}%)", creator_percent));
    } else if creator_percent < 10.0 {
        score += 4.0;
        reasons.push(format!(
            "+4  — Creator holds {:.2}% (moderate)",
            creator_percent
        ));
        warnings.push(format!(
            "⚠ Moderate creator holdings ({:.2}%)",
            creator_percent
        ));
    } else if creator_percent < 25.0 {
        score += 2.0;
        reasons.push(format!(
            "+2  — Creator holds {:.2}% (high)",
            creator_percent
        ));
        warnings.push(format!(
            "⚠ High creator holdings ({:.2}%) - potential rug pull risk",
            creator_percent
        ));
    } else {
        reasons.push(format!(
            "+0  — Creator holds {:.2}% (extreme rug pull risk)",
            creator_percent
        ));
        warnings.push(format!(
            "⚠ CRITICAL: Creator holds {:.2}% - extreme rug pull risk",
            creator_percent
        ));
    }

    score
}

fn score_metadata_flags(
    security: &BirdeyeTokenSecurity,
    warnings: &mut Vec<String>,
    positive_signals: &mut Vec<String>,
    reasons: &mut Vec<String>,
) -> f64 {
    let mut score = 0.0;

    let mutable = security.mutable_metadata.unwrap_or(true);
    if !mutable {
        score += 8.0;
        reasons.push("+8  — Metadata is immutable".to_string());
        positive_signals.push("✓ Immutable metadata (cannot be changed by creator)".to_string());
    } else {
        reasons
            .push("+0  — Metadata is mutable (creator can change name/symbol/image)".to_string());
        warnings.push("⚠ Mutable metadata - creator can change token details".to_string());
    }

    let has_transfer_fee = security.transfer_fee_enable.unwrap_or(false);
    if !has_transfer_fee {
        score += 5.0;
        reasons.push("+5  — No transfer fees".to_string());
    } else {
        reasons.push("+0  — Transfer fees active (value drains on each trade)".to_string());
        warnings.push("⚠ Token has transfer fees - value drains on each trade".to_string());
    }

    let is_token2022 = security.is_token2022.unwrap_or(false);
    if !is_token2022 {
        score += 2.0;
        reasons.push("+2  — Standard SPL token".to_string());
        positive_signals.push("✓ Standard SPL token (well-established)".to_string());
    } else {
        reasons.push(
            "+0  — Token2022 program (extended features, not penalized beyond score)".to_string(),
        );
        positive_signals
            .push("ℹ Token2022 program (newer standard with extended features)".to_string());
    }

    score
}

fn score_trust_signals(
    security: &BirdeyeTokenSecurity,
    warnings: &mut Vec<String>,
    positive_signals: &mut Vec<String>,
    reasons: &mut Vec<String>,
) -> f64 {
    let mut score = 0.0;

    let on_jup_strict = security.jup_strict_list.unwrap_or(false);
    if on_jup_strict {
        score += 10.0;
        reasons.push("+10 — Listed on Jupiter strict list (manually verified)".to_string());
        positive_signals.push("✓ Listed on Jupiter strict list (manually verified)".to_string());
    } else {
        reasons.push("+0  — Not on Jupiter strict list".to_string());
    }

    let is_fake = security.fake_token.unwrap_or(false);
    if is_fake {
        reasons.push("→ Score forced to 0: token flagged as FAKE by Birdeye".to_string());
        warnings.push("⚠ CRITICAL: Token flagged as FAKE by Birdeye".to_string());
        return 0.0;
    } else {
        score += 3.0;
        reasons.push("+3  — Not flagged as fake by Birdeye".to_string());
    }

    let is_true = security.is_true_token.unwrap_or(false);
    if is_true {
        score += 2.0;
        reasons.push("+2  — Validated as legitimate token (isTrueToken)".to_string());
        positive_signals.push("✓ Validated as legitimate token".to_string());
    } else {
        reasons.push("+0  — Not validated as true token (isTrueToken = false)".to_string());
    }

    score
}

fn check_mint_authority_revoked(security: &BirdeyeTokenSecurity) -> bool {
    match &security.owner_address {
        None => true,
        Some(s) => s == "null" || s.is_empty(),
    }
}

fn check_freeze_authority_revoked(security: &BirdeyeTokenSecurity) -> bool {
    match &security.freeze_authority {
        None => true,
        Some(s) => s == "null" || s.is_empty(),
    }
}

fn classify_risk_level(score: f64) -> RiskLevel {
    if score >= 90.0 {
        RiskLevel::Excellent
    } else if score >= 75.0 {
        RiskLevel::Good
    } else if score >= 60.0 {
        RiskLevel::Moderate
    } else if score >= 40.0 {
        RiskLevel::Poor
    } else {
        RiskLevel::Critical
    }
}

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

    fn clean_security() -> BirdeyeTokenSecurity {
        let mut s = BirdeyeTokenSecurity::default();
        s.non_transferable = Some(false);
        s.fake_token = Some(false);
        s.owner_address = None;
        s.freeze_authority = None;
        s
    }

    #[test]
    fn test_pumpfun_platform_fields_in_score() {
        let mut security = clean_security();
        security.top10_holder_percent = Some(100.0);

        let score = score(
            "J1YNFvLWJ7EQ3vrv2KPTQsEMNo3FjsQxZoaUz4y1pump",
            &security,
            None,
        );

        assert_eq!(score.launch_platform, "Pump.fun");
        assert_eq!(score.platform_trust_level, "Audited");
        assert_eq!(score.details.launch_platform, "Pump.fun");
        assert_eq!(score.details.platform_trust_level, "Audited");
    }

    #[test]
    fn test_unknown_platform_fields_in_score() {
        let security = clean_security();
        let score = score(
            "So11111111111111111111111111111111111111112",
            &security,
            None,
        );

        assert_eq!(score.launch_platform, "Unknown");
        assert_eq!(score.platform_trust_level, "Unknown");
    }

    #[test]
    fn test_pumpfun_bonding_curve_no_hard_stop() {
        let mut security = clean_security();
        security.top10_holder_percent = Some(100.0);
        security.creator_percentage = Some(0.0);

        let score = score("mintaddresspump", &security, None);

        assert!(!score.hard_stop_triggered);
        assert!(score.hard_stop_violations.is_empty());
    }

    #[test]
    fn test_pumpfun_holder_score_is_neutral_override() {
        let mut security = clean_security();
        security.top10_holder_percent = Some(100.0);

        let score = score("mintaddresspump", &security, None);

        assert_eq!(score.score_breakdown.holder_distribution, 10.0);
    }

    #[test]
    fn test_pumpfun_notes_appear_in_positive_signals() {
        let mut security = clean_security();
        security.top10_holder_percent = Some(100.0);

        let score = score("mintaddresspump", &security, None);

        assert!(
            score
                .positive_signals
                .iter()
                .any(|s| s.contains("Pump.fun"))
        );
    }

    #[test]
    fn test_unknown_token_100pct_concentration_triggers_hard_stop() {
        let mut security = clean_security();
        security.top10_holder_percent = Some(100.0);

        let score = score("SomeRandomTokenMint", &security, None);

        assert!(score.hard_stop_triggered);
        assert!(
            score
                .hard_stop_violations
                .iter()
                .any(|v| v.category == "Holder Distribution")
        );
    }

    #[test]
    fn test_score_holder_distribution_unknown_platform_normal_path() {
        let mut security = BirdeyeTokenSecurity::default();
        security.top10_holder_percent = Some(15.0);
        security.creator_percentage = Some(0.5);

        let profile = PlatformProfile::unknown();
        let mut warnings = Vec::new();
        let mut positive_signals = Vec::new();
        let mut reasons = Vec::new();

        let score = score_holder_distribution(
            &security,
            &profile,
            &mut warnings,
            &mut positive_signals,
            &mut reasons,
        );

        assert_eq!(score, 20.0);
        assert!(warnings.is_empty());
    }

    #[test]
    fn test_score_holder_distribution_pumpfun_returns_override() {
        let mut security = BirdeyeTokenSecurity::default();
        security.top10_holder_percent = Some(100.0);
        security.creator_percentage = Some(0.0);

        let pumpfun = crate::platform::detect_platform("mintpump", &security);
        let mut warnings = Vec::new();
        let mut positive_signals = Vec::new();
        let mut reasons = Vec::new();

        let score = score_holder_distribution(
            &security,
            &pumpfun,
            &mut warnings,
            &mut positive_signals,
            &mut reasons,
        );

        assert_eq!(score, 10.0);
        assert!(warnings.is_empty());
        assert!(positive_signals.iter().any(|s| s.contains("bonding curve")));
    }
}
