"""
Tests for Token Clone Scanner (clone_scanner.py)

Tests the core logic: string similarity, name similarity scoring,
risk labeling, data models, and fast_check surrogate.
Network tests are skipped in offline mode.
"""

import os
import sys
import unittest

# Add parent to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from app.clone_scanner import (
    CloneReport,
    CloneScanner,
    SimilarToken,
    name_similarity_score,
    string_similarity,
)


class TestStringHelpers(unittest.TestCase):
    """Core string similarity utilities."""

    def test_identical_strings(self):
        self.assertEqual(string_similarity("Pepe", "Pepe"), 1.0)

    def test_case_insensitive(self):
        self.assertAlmostEqual(string_similarity("Pepe", "pepe"), 1.0)

    def test_completely_different(self):
        self.assertAlmostEqual(string_similarity("Pepe", "Bitcoin"), 0.0, places=1)

    def test_partial_match(self):
        sim = string_similarity("Pepe", "Pepe 2.0")
        self.assertGreater(sim, 0.3)
        self.assertLess(sim, 1.0)

    def test_empty_strings(self):
        self.assertEqual(string_similarity("", "Pepe"), 0.0)
        self.assertEqual(string_similarity("Pepe", ""), 0.0)
        self.assertEqual(string_similarity("", ""), 0.0)


class TestNameSimilarity(unittest.TestCase):
    """Name similarity scoring with clone patterns."""

    def test_identical_names(self):
        self.assertEqual(name_similarity_score("Pepe", "Pepe"), 1.0)

    def test_prefix_clone(self):
        """'Baby Pepe' should score high vs 'Pepe'."""
        score = name_similarity_score("Baby Pepe", "Pepe")
        self.assertGreaterEqual(score, 0.65)

    def test_suffix_clone(self):
        """'Pepe v2' should score very high vs 'Pepe'."""
        score = name_similarity_score("Pepe v2", "Pepe")
        self.assertGreaterEqual(score, 0.85)

    def test_known_v2_pattern(self):
        score = name_similarity_score("SafeMoon", "SafeMoon 2.0")
        self.assertGreaterEqual(score, 0.85)

    def test_different_tokens(self):
        score = name_similarity_score("Bitcoin", "Pepe")
        self.assertLess(score, 0.3)


class TestCloneReport(unittest.TestCase):
    """CloneReport data model and helpers."""

    def test_to_dict_structure(self):
        report = CloneReport(
            token_address="0xabc123",
            chain="ethereum",
            name="TestToken",
            symbol="TEST",
            clone_score=45.5,
            risk_label="medium",
            similar_tokens=[
                SimilarToken(
                    address="0xdef456",
                    chain="ethereum",
                    name="TestToken Clone",
                    symbol="TSTC",
                    name_similarity=0.85,
                    symbol_similarity=0.6,
                )
            ],
            matched_keywords=["pepe"],
            unverified_contract=True,
            same_deployer_other_tokens=12,
        )
        d = report.to_dict()
        self.assertEqual(d["token_address"], "0xabc123")
        self.assertEqual(d["clone_score"], 45.5)
        self.assertEqual(d["risk_label"], "medium")
        self.assertEqual(len(d["similar_tokens"]), 1)
        self.assertEqual(d["matched_keywords"], ["pepe"])
        self.assertTrue(d["unverified_contract"])
        self.assertEqual(d["same_deployer_other_tokens"], 12)

        # Check signals sub-dict
        self.assertIn("bytecode_similarity", d["signals"])
        self.assertIn("name_similarity", d["signals"])
        self.assertIn("deployer_risk", d["signals"])
        self.assertIn("metadata_risk", d["signals"])

    def test_summary_format(self):
        report = CloneReport(
            token_address="0xabc123def4567890123456789012345678901234",
            chain="ethereum",
            name="ShadyToken",
            symbol="SHADY",
            clone_score=72.3,
            risk_label="high",
            similar_tokens=[
                SimilarToken(
                    address="0xdead",
                    chain="ethereum",
                    name="RealToken",
                    symbol="REAL",
                    name_similarity=0.9,
                )
            ],
            matched_keywords=["pepe", "moon"],
            unverified_contract=True,
        )
        summary = report.summary()
        self.assertIn("HIGH", summary)
        self.assertIn("72", summary)
        self.assertIn("ShadyToken", summary)
        self.assertIn("1 similar tokens", summary)

    def test_risk_label_none(self):
        report = CloneReport(token_address="0xaaa", chain="ethereum", clone_score=0.0)
        scanner = CloneScanner()
        report.risk_label = scanner._label_risk(0.0)
        self.assertEqual(report.risk_label, "none")

    def test_risk_label_critical(self):
        report = CloneReport(token_address="0xaaa", chain="ethereum", clone_score=85.0)
        scanner = CloneScanner()
        report.risk_label = scanner._label_risk(85.0)
        self.assertEqual(report.risk_label, "critical")

    def test_risk_label_brackets(self):
        report = CloneReport(token_address="0xaaa", chain="ethereum", clone_score=25.0)
        scanner = CloneScanner()
        report.risk_label = scanner._label_risk(25.0)
        self.assertEqual(report.risk_label, "medium")


class TestSimilarToken(unittest.TestCase):
    """SimilarToken helper properties."""

    def test_overall_similarity(self):
        t = SimilarToken(
            address="0xabc",
            chain="ethereum",
            name="TokenA",
            symbol="TKA",
            name_similarity=0.9,
            symbol_similarity=0.5,
        )
        self.assertEqual(t.overall_similarity, 0.9)

    def test_overall_uses_symbol_when_higher(self):
        t = SimilarToken(
            address="0xabc",
            chain="ethereum",
            name="TokenA",
            symbol="TKA",
            name_similarity=0.4,
            symbol_similarity=0.95,
        )
        self.assertEqual(t.overall_similarity, 0.95)


class TestScannerScoring(unittest.TestCase):
    """Unit tests for scoring methods (no network)."""

    def setUp(self):
        self.scanner = CloneScanner()

    def test_score_bytecode_unverified(self):
        score = self.scanner._score_bytecode_risk(True, {})
        self.assertEqual(score, 40)  # 40 for unverified

    def test_score_bytecode_verified(self):
        score = self.scanner._score_bytecode_risk(False, {"other_unverified": 0})
        self.assertEqual(score, 0.0)

    def test_score_bytecode_mass_deployer(self):
        score = self.scanner._score_bytecode_risk(True, {"other_unverified": 10, "other_tokens": 30})
        # 40 (unverified) + 20 (many unverified) + 15 (mass deployer) = 75
        self.assertEqual(score, 75)

    def test_score_name_no_keywords(self):
        score = self.scanner._score_name_similarity([], [])
        self.assertEqual(score, 0.0)

    def test_score_name_with_keywords(self):
        score = self.scanner._score_name_similarity([], ["pepe", "moon"])
        self.assertEqual(score, 25)

    def test_score_name_with_high_similarity(self):
        t = SimilarToken("0x1", "eth", "Clone", "CLO", name_similarity=0.95)
        score = self.scanner._score_name_similarity([t], [])
        # 1 high-sim match = 20
        self.assertEqual(score, 20)

    def test_score_deployer_low(self):
        score = self.scanner._score_deployer_risk({"other_tokens": 2})
        self.assertEqual(score, 0.0)

    def test_score_deployer_medium(self):
        score = self.scanner._score_deployer_risk({"other_tokens": 10})
        self.assertEqual(score, 15)

    def test_score_deployer_high(self):
        score = self.scanner._score_deployer_risk({"other_tokens": 30})
        self.assertEqual(score, 30)

    def test_score_deployer_critical(self):
        score = self.scanner._score_deployer_risk({"other_tokens": 100})
        self.assertEqual(score, 40)

    def test_score_deployer_clone_flag(self):
        score = self.scanner._score_deployer_risk({"other_tokens": 5, "clone_deployer": True})
        # 5 other tokens = no bonus (needs >20), +30 clone flag = 30
        self.assertEqual(score, 30)

    def test_score_metadata_missing(self):
        score = self.scanner._score_metadata_risk({}, [], [])
        # Empty dict = no name or symbol = 20+20 = 40
        self.assertEqual(score, 40)

    def test_score_metadata_with_social(self):
        score = self.scanner._score_metadata_risk({"name": "T", "symbol": "S", "social": {"twitter": "@x"}}, [], [])
        self.assertEqual(score, 10)  # missing website

    def test_score_metadata_full(self):
        score = self.scanner._score_metadata_risk(
            {
                "name": "Test",
                "symbol": "TST",
                "social": {"twitter": "@x", "telegram": "@g"},
                "website": "https://t.com",
            },
            [],
            [],
        )
        self.assertEqual(score, 0.0)

    def test_compute_composite_score(self):
        report = CloneReport(
            token_address="0x1",
            chain="eth",
            bytecode_similarity_score=100,
            name_similarity_score=50,
            deployer_risk_score=0,
            metadata_risk_score=0,
        )
        # 0.25*100 + 0.35*50 + 0.25*0 + 0.15*0 = 25 + 17.5 = 42.5
        self.assertEqual(self.scanner._compute_clone_score(report), 42.5)

    def test_well_known_by_name(self):
        self.assertTrue(self.scanner._is_well_known("Bitcoin", "BTC"))
        self.assertTrue(self.scanner._is_well_known("Ethereum", "ETH"))

    def test_well_known_by_symbol(self):
        self.assertTrue(self.scanner._is_well_known("Random", "ETH"))

    def test_not_well_known(self):
        self.assertFalse(self.scanner._is_well_known("ShadyCoin", "SHADY"))

    def test_valid_evm_address(self):
        self.assertTrue(self.scanner._validate_address("0x1234567890abcdef1234567890abcdef12345678", "ethereum"))

    def test_valid_solana_address(self):
        self.assertTrue(self.scanner._validate_address("So11111111111111111111111111111111111111112", "solana"))

    def test_invalid_address(self):
        self.assertFalse(self.scanner._validate_address("not-an-address", "ethereum"))


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