"""
Tests for TokenSupplyAnalyzer
"""

import unittest
from datetime import UTC, datetime

from app.token_supply_analyzer import (
    LockStatus,
    SupplyRisk,
    TokenSupplyProfile,
    _analyze_concentration,
    _analyze_liquidity,
    _analyze_supply_mechanics,
    _classify_risk,
    _compute_overall_risk,
    _compute_pair_age_days,
    _detect_chain,
    _normalize_address,
)


class TestSupplyScoring(unittest.TestCase):
    """Test the supply mechanics analysis engine."""

    def test_healthy_supply(self) -> None:
        """A token with normal supply metrics should score low risk."""
        profile = TokenSupplyProfile(address="0xtest123")
        profile.total_supply = 1_000_000_000
        profile.circulating_supply = 800_000_000
        profile.max_supply = 1_000_000_000
        profile.burned_supply = 50_000_000
        profile.has_mint_function = False
        profile.is_deflationary = True

        _analyze_supply_mechanics(profile)
        self.assertLess(
            profile.supply_risk_score,
            30,
            f"Healthy supply should score low, got {profile.supply_risk_score}",
        )

    def test_unlimited_mint_supply(self) -> None:
        """A token with no max supply and mint function should score high."""
        profile = TokenSupplyProfile(address="0xevil123")
        profile.total_supply = 1_000_000
        profile.circulating_supply = 100_000
        profile.max_supply = 0
        profile.has_mint_function = True
        profile.mint_function_type = "unlimited"

        _analyze_supply_mechanics(profile)
        self.assertGreater(
            profile.supply_risk_score,
            50,
            f"Unlimited mint should score high, got {profile.supply_risk_score}",
        )
        self.assertIn("unlimited_mint_function", str(profile.patterns_detected))

    def test_supply_exceeds_max(self) -> None:
        """A token where total > max supply should be critical."""
        profile = TokenSupplyProfile(address="0xoverflow")
        profile.total_supply = 2_000_000_000
        profile.circulating_supply = 1_500_000_000
        profile.max_supply = 1_000_000_000

        _analyze_supply_mechanics(profile)
        self.assertGreaterEqual(
            profile.supply_risk_score,
            40,
            f"Supply overflow should score high, got {profile.supply_risk_score}",
        )
        self.assertIn("supply_exceeds_max", str(profile.patterns_detected))

    def test_dilution_risk(self) -> None:
        """A token with <30% circ/total ratio should flag dilution."""
        profile = TokenSupplyProfile(address="0xdilute")
        profile.total_supply = 10_000_000
        profile.circulating_supply = 2_000_000

        _analyze_supply_mechanics(profile)
        self.assertIn("high_dilution_risk", str(profile.patterns_detected))

    def test_large_mint_headroom(self) -> None:
        """A token with >50% of max supply remaining should flag."""
        profile = TokenSupplyProfile(address="0xmintroom")
        profile.total_supply = 100_000
        profile.circulating_supply = 80_000
        profile.max_supply = 1_000_000
        profile.has_mint_function = True

        _analyze_supply_mechanics(profile)
        self.assertIn("large_mint_headroom", str(profile.patterns_detected))


class TestConcentrationScoring(unittest.TestCase):
    """Test holder concentration analysis."""

    def test_extreme_concentration(self) -> None:
        """Top 10 holding >99% should be critical."""
        profile = TokenSupplyProfile(address="0xconcentrated")
        profile.top_10_holder_pct = 99.5
        profile.top_50_holder_pct = 99.9

        _analyze_concentration(profile)
        self.assertIn("extreme_concentration:top_10_holds_>99%", str(profile.patterns_detected))
        self.assertIn(profile.concentration_risk, (SupplyRisk.HIGH, SupplyRisk.CRITICAL))

    def test_deployer_holds_majority(self) -> None:
        """Deployer holding >50% should be critical."""
        profile = TokenSupplyProfile(address="0xdeployer_owns")
        profile.deployer_hold_pct = 75.0
        profile.top_10_holder_pct = 85.0

        _analyze_concentration(profile)
        self.assertIn("deployer_holds_majority", str(profile.patterns_detected))

    def test_no_concentration(self) -> None:
        """No significant concentration should score low."""
        profile = TokenSupplyProfile(address="0xfair_dist")
        profile.deployer_hold_pct = 2.0
        profile.top_10_holder_pct = 15.0
        profile.top_50_holder_pct = 30.0

        _analyze_concentration(profile)
        self.assertLess(
            profile.concentration_score,
            20,
            f"Fair distribution should score low, got {profile.concentration_score}",
        )


class TestLiquidityScoring(unittest.TestCase):
    """Test liquidity analysis."""

    def test_liquidity_removed(self) -> None:
        """Removed liquidity should be critical."""
        profile = TokenSupplyProfile(address="0xrugpull")
        profile.lock_status = LockStatus.REMOVED

        _analyze_liquidity(profile)
        self.assertIn("liquidity_removed", str(profile.patterns_detected))
        self.assertGreater(profile.liquidity_score, 40)

    def test_liquidity_locked(self) -> None:
        """Locked liquidity should reduce risk."""
        profile = TokenSupplyProfile(address="0xsafe")
        profile.lock_status = LockStatus.LOCKED
        profile.liquidity_lock_expiry = "2027-01-01"
        profile.pair_age_days = 60
        profile.pair_liquidity_usd = 200_000

        _analyze_liquidity(profile)
        self.assertIn("liquidity_locked", str(profile.patterns_detected))
        self.assertLess(profile.liquidity_score, 20)

    def test_very_new_pair(self) -> None:
        """Very new pair should be flagged."""
        profile = TokenSupplyProfile(address="0xnew_token")
        profile.pair_age_days = 0.5
        profile.pair_liquidity_usd = 500

        _analyze_liquidity(profile)
        self.assertIn("very_new_pair", str(profile.patterns_detected))
        self.assertIn("very_low_liquidity", str(profile.patterns_detected))


class TestOverallRisk(unittest.TestCase):
    """Test overall risk computation."""

    def test_critical_token(self) -> None:
        """A token with multiple red flags should be critical."""
        profile = TokenSupplyProfile(address="0xscam_token")
        profile.has_mint_function = True
        profile.mint_function_type = "unlimited"
        profile.has_dynamic_tax = True
        profile.has_blacklist = True
        profile.buy_tax_pct = 15.0
        profile.sell_tax_pct = 25.0
        profile.lock_status = LockStatus.REMOVED
        profile.top_10_holder_pct = 99.0
        profile.is_renounced = False
        profile.has_proxy_admin = True

        _analyze_supply_mechanics(profile)
        _analyze_concentration(profile)
        _analyze_liquidity(profile)
        _compute_overall_risk(profile)
        self.assertIn(
            profile.overall_risk,
            (SupplyRisk.HIGH, SupplyRisk.CRITICAL),
            f"Scam token should be high/critical, got {profile.overall_risk}",
        )

    def test_safe_token(self) -> None:
        """A token with good practices should be safe/low."""
        profile = TokenSupplyProfile(address="0xgood_token")
        profile.total_supply = 1_000_000
        profile.circulating_supply = 950_000
        profile.max_supply = 1_000_000
        profile.burned_supply = 50_000
        profile.has_mint_function = False
        profile.is_deflationary = True
        profile.buy_tax_pct = 1.0
        profile.sell_tax_pct = 1.0
        profile.lock_status = LockStatus.LOCKED
        profile.is_renounced = True
        profile.top_10_holder_pct = 20.0
        profile.top_50_holder_pct = 35.0
        profile.deployer_hold_pct = 3.0
        profile.pair_age_days = 120
        profile.pair_liquidity_usd = 500_000

        _analyze_supply_mechanics(profile)
        _analyze_concentration(profile)
        _analyze_liquidity(profile)
        _compute_overall_risk(profile)
        self.assertIn(
            profile.overall_risk,
            (SupplyRisk.SAFE, SupplyRisk.LOW),
            f"Safe token should be safe/low, got {profile.overall_risk}",
        )


class TestUtilities(unittest.TestCase):
    """Test utility functions."""

    def test_detect_chain(self) -> None:
        """Chain detection should work for common formats."""
        self.assertEqual(_detect_chain("0xabcd1234"), "ethereum")
        self.assertEqual(
            _detect_chain("AbCdEf1234567890AbCdEf1234567890AbCdEf1234567890AbCdEf1234567890"),
            "solana",
        )
        self.assertEqual(_detect_chain("unknown"), "unknown")

    def test_normalize_address(self) -> None:
        """Address normalization should lowercase and strip."""
        self.assertEqual(_normalize_address("0xABC123"), "0xabc123")
        self.assertEqual(_normalize_address("  0xABC  "), "0xabc")

    def test_risk_classification(self) -> None:
        """Risk classification boundaries."""
        self.assertEqual(_classify_risk(0), SupplyRisk.SAFE)
        self.assertEqual(_classify_risk(10), SupplyRisk.SAFE)
        self.assertEqual(_classify_risk(11), SupplyRisk.LOW)
        self.assertEqual(_classify_risk(25), SupplyRisk.LOW)
        self.assertEqual(_classify_risk(26), SupplyRisk.MEDIUM)
        self.assertEqual(_classify_risk(50), SupplyRisk.MEDIUM)
        self.assertEqual(_classify_risk(51), SupplyRisk.HIGH)
        self.assertEqual(_classify_risk(75), SupplyRisk.HIGH)
        self.assertEqual(_classify_risk(76), SupplyRisk.CRITICAL)

    def test_pair_age_computation(self) -> None:
        """Pair age from Unix ms timestamp."""
        now = datetime.now(tz=UTC)
        one_day_ago = int((now.timestamp() - 86400) * 1000)
        age = _compute_pair_age_days(one_day_ago)
        self.assertAlmostEqual(age, 1.0, delta=0.1)

        # Zero timestamp
        self.assertEqual(_compute_pair_age_days(0), 0.0)

        # Future timestamp
        future = int((now.timestamp() + 86400) * 1000)
        self.assertEqual(_compute_pair_age_days(future), 0.0)


class TestOwnershipAndTax(unittest.TestCase):
    """Test ownership and tax analysis in overall risk."""

    def test_proxy_admin_risk(self) -> None:
        """Proxy admin should flag upgrade risk."""
        profile = TokenSupplyProfile(address="0xproxy")
        profile.has_proxy_admin = True
        profile.is_renounced = False

        _compute_overall_risk(profile)
        self.assertIn("proxy_admin:contract_can_be_upgraded", str(profile.patterns_detected))

    def test_multisig_benefit(self) -> None:
        """Multi-sig ownership should reduce risk."""
        profile = TokenSupplyProfile(address="0xmultisig")
        profile.has_multisig = True
        profile.is_renounced = False

        _compute_overall_risk(profile)
        self.assertIn("multisig_owner", str(profile.patterns_detected))

    def test_dynamic_tax_detection(self) -> None:
        """Dynamic tax should be flagged."""
        profile = TokenSupplyProfile(address="0xtaxy")
        profile.has_dynamic_tax = True
        profile.has_blacklist = True

        _compute_overall_risk(profile)
        self.assertIn("dynamic_tax:tax_can_change", str(profile.patterns_detected))
        self.assertIn("blacklist_function", str(profile.patterns_detected))

    def test_high_sell_tax(self) -> None:
        """Very high sell tax should flag honeypot risk."""
        profile = TokenSupplyProfile(address="0xhoneypot")
        profile.sell_tax_pct = 99.0

        _compute_overall_risk(profile)
        self.assertIn("very_high_sell_tax:99.0%", str(profile.patterns_detected))


if __name__ == "__main__":
    unittest.main()
