#!/usr/bin/env python3
"""#9 — Token Laundry Matcher. Finds copycat tokens by code similarity, deployer patterns,
liquidity profile, marketing tactics. Spots scams before they launch."""

import os

import httpx
from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel

router = APIRouter(prefix="/api/v1/laundry-matcher", tags=["laundry-matcher"])

BACKEND = os.environ.get("BACKEND_URL", "http://localhost:8000")


class LaundryMatch(BaseModel):
    address: str
    chain: str
    similarity_score: float  # 0-1
    match_reasons: list[str]
    risk_inherited: int


def _name_similarity(a: str, b: str) -> float:
    """Simple trigram similarity for token names."""

    def trigrams(s):
        s = s.lower().replace(" ", "")
        return {s[i : i + 3] for i in range(len(s) - 2)} if len(s) >= 3 else {s}

    ta = trigrams(a)
    tb = trigrams(b)
    if not ta or not tb:
        return 0.0
    return len(ta & tb) / len(ta | tb)


async def _fetch_token_pairs(chain: str, max_pairs: int = 50) -> list[dict]:
    """Fetch recent token pairs from DexScreener for comparison."""
    try:
        async with httpx.AsyncClient(timeout=10) as client:
            resp = await client.get(f"https://api.dexscreener.com/latest/dex/search?q={chain}")
            if resp.status_code != 200:
                return []
            return resp.json().get("pairs", [])[:max_pairs]
    except Exception:
        return []


@router.get("/match/{address}")
async def find_matches(
    address: str,
    chain: str = Query("solana"),
    min_similarity: float = Query(0.3, ge=0, le=1),
    limit: int = Query(10, le=25),
):
    """Find tokens similar to the given address. Returns top-N copycat matches."""
    # Get reference token data
    try:
        async with httpx.AsyncClient(timeout=15) as client:
            resp = await client.post(
                f"{BACKEND}/api/v1/token/scan",
                json={"token_address": address, "chain": chain},
                headers={"X-RMI-Key": "rmi-internal-2026"},
            )
            if resp.status_code != 200:
                raise HTTPException(502, "Scanner unavailable")
            reference = resp.json()
    except Exception as e:
        raise HTTPException(502, f"Scan failed: {e}")

    ref_name = reference.get("free", {}).get("name", "") or reference.get("symbol", "")
    reference.get("pro", {}).get("deployer_address", "")

    # Fetch comparison pool
    pairs = await _fetch_token_pairs(chain, max_pairs=50)

    matches: list[dict] = []
    for p in pairs:
        base = p.get("baseToken", {})
        target_name = base.get("name", "") or base.get("symbol", "")
        target_addr = p.get("pairAddress", "") or base.get("address", "")
        if target_addr == address:
            continue

        # Compute similarity
        name_sim = _name_similarity(ref_name, target_name)
        reasons: list[str] = []
        if name_sim > 0.5:
            reasons.append(f"Name similarity: {name_sim:.0%}")
        if p.get("liquidity", {}).get("usd", 0) == 0:
            reasons.append("Zero liquidity pattern")

        similarity = name_sim  # Primary metric
        # Could add: code similarity, deployer overlap, launch timing

        if similarity >= min_similarity:
            matches.append(
                {
                    "address": target_addr,
                    "chain": chain,
                    "symbol": base.get("symbol", "?"),
                    "name": target_name,
                    "similarity_score": round(similarity, 3),
                    "match_reasons": reasons,
                    "risk_inherited": reference.get("safety_score", 50),
                    "price_usd": float(p.get("priceUsd", 0) or 0),
                    "liquidity_usd": p.get("liquidity", {}).get("usd", 0) or 0,
                }
            )

    matches.sort(key=lambda m: m["similarity_score"], reverse=True)
    return {
        "reference": {
            "address": address,
            "chain": chain,
            "name": ref_name,
            "safety_score": reference.get("safety_score", 50),
        },
        "matches": matches[:limit],
        "total_candidates_scanned": len(pairs),
    }


@router.get("/scan-similar/{address}")
async def scan_and_match(address: str, chain: str = Query("solana")):
    """Scan the address AND find its matches in a single call."""
    async with httpx.AsyncClient(timeout=20) as client:
        scan_task = client.post(
            f"{BACKEND}/api/v1/token/scan",
            json={"token_address": address, "chain": chain},
            headers={"X-RMI-Key": "rmi-internal-2026"},
        )

        # Also kick off match search
        pairs = await _fetch_token_pairs(chain)

        scan_resp = await scan_task
        if scan_resp.status_code != 200:
            raise HTTPException(502, "Scan failed")
        scan = scan_resp.json()

    # Quick match
    ref_name = scan.get("free", {}).get("name", "") or scan.get("symbol", "")
    matches = []
    for p in pairs[:50]:
        base = p.get("baseToken", {})
        sim = _name_similarity(ref_name, base.get("name", "") or base.get("symbol", ""))
        if sim > 0.5:
            matches.append(
                {"address": p.get("pairAddress", ""), "symbol": base.get("symbol", "?"), "similarity": round(sim, 3)}
            )

    return {"scan": scan, "copycat_matches": sorted(matches, key=lambda m: m["similarity"], reverse=True)[:10]}
