"""
Darkroom Airdrop Engine
=======================
Advanced token distribution system with:
  • Snapshot-based airdrops (holdings of previous token)
  • Team/development allocation (configurable % of supply)
  • Anti-sniper protection (blacklist, tx limits, trading delays)
  • Vesting schedules for team tokens
  • Multi-chain support (EVM, Solana, TRON)
  • Batch distribution for gas efficiency

All operations are admin-only and stay in /root/tools/ — never committed to git.

Usage:
  POST /api/v1/admin/tokens/airdrop/snapshot    — Create snapshot from existing token
  POST /api/v1/admin/tokens/airdrop/execute     — Execute airdrop distribution
  POST /api/v1/admin/tokens/airdrop/team          — Allocate team/dev tokens
  POST /api/v1/admin/tokens/airdrop/vesting     — Set up vesting for team
  GET  /api/v1/admin/tokens/airdrop/{id}/status  — Check airdrop status
  POST /api/v1/admin/tokens/airdrop/antisniper   — Enable anti-sniper protection
"""

from __future__ import annotations

import json
import logging
import os
import time
from dataclasses import asdict, dataclass, field
from datetime import datetime
from typing import Any

from app.token_deployer import TokenDeployerFactory, TokenDeployment, get_storage

logger = logging.getLogger("darkroom_airdrop")


# ── Data Models ───────────────────────────────────────────────


@dataclass
class AirdropRecipient:
    """Single recipient in an airdrop."""

    address: str
    amount: str
    reason: str = ""  # e.g., "holder_of_CRM_v1", "team_allocation", "marketing"
    claimed: bool = False
    claim_tx: str = ""
    claimed_at: str | None = None


@dataclass
class AirdropSnapshot:
    """Snapshot of token holders at a specific block/time."""

    snapshot_id: str
    source_token: str
    source_chain: str
    block_number: int | None = None
    timestamp: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    holders: list[AirdropRecipient] = field(default_factory=list)
    total_holders: int = 0
    total_supply_snapshotted: str = "0"
    excluded_addresses: list[str] = field(default_factory=list)
    min_holdings: str = "0"
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class AirdropCampaign:
    """Full airdrop campaign with distribution rules."""

    campaign_id: str
    deployment_id: str
    snapshot_id: str
    chain: str
    status: str = "pending"  # pending, active, paused, completed, cancelled
    distribution_type: str = "snapshot"  # snapshot, manual, team, marketing
    recipients: list[AirdropRecipient] = field(default_factory=list)
    total_amount: str = "0"
    distributed_amount: str = "0"
    remaining_amount: str = "0"
    team_allocation_percent: float = 0.0
    team_vesting_months: int = 0
    team_cliff_months: int = 0
    anti_sniper_enabled: bool = True
    trading_delay_blocks: int = 0
    max_wallet_percent: float = 0.0
    max_tx_percent: float = 0.0
    blacklist_preloaded: list[str] = field(default_factory=list)
    created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    started_at: str | None = None
    completed_at: str | None = None
    tx_hashes: list[str] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict:
        d = asdict(self)
        return d


@dataclass
class VestingSchedule:
    """Vesting schedule for team/dev tokens."""

    schedule_id: str
    deployment_id: str
    beneficiary: str
    total_amount: str
    claimed_amount: str = "0"
    start_date: str = ""
    cliff_months: int = 0
    vesting_months: int = 0
    monthly_release: str = "0"
    status: str = "active"  # active, completed, revoked
    tx_hashes: list[str] = field(default_factory=list)


# ── Anti-Sniper Protection ────────────────────────────────────


class AntiSniperProtection:
    """
    Protect token launches from snipers and bots.

    Features:
      1. Pre-launch blacklist (known bot addresses)
      2. Trading delay (blocks after launch before trading)
      3. Max wallet / tx limits (prevent accumulation)
      4. Dynamic blacklist (detect and ban sandwich bots)
      5. Whitelist-only mode (initially)
    """

    # Known bot/sniper patterns (addresses and creation patterns)
    KNOWN_BOT_PATTERNS = [
        "0x0000000000000000000000000000000000000000",  # Burn address
    ]

    @classmethod
    async def apply_protection(
        cls,
        deployer: Any,
        contract_address: str,
        deployment: TokenDeployment,
        blacklist_addresses: list[str] | None = None,
        trading_delay_blocks: int = 0,
        max_wallet_percent: float = 0.0,
        max_tx_percent: float = 0.0,
    ) -> dict[str, Any]:
        """Apply full anti-sniper protection suite to a token."""
        results = {
            "blacklist_applied": 0,
            "trading_delayed": False,
            "max_wallet_set": False,
            "max_tx_set": False,
            "tx_hashes": [],
        }

        # 1. Blacklist known bots + provided addresses
        all_blacklist = set(cls.KNOWN_BOT_PATTERNS)
        if blacklist_addresses:
            all_blacklist.update(blacklist_addresses)

        for addr in all_blacklist:
            try:
                tx = await deployer.blacklist_add(contract_address, addr)
                results["tx_hashes"].append(tx)
                results["blacklist_applied"] += 1
                logger.info(f"Anti-sniper: blacklisted {addr}")
            except Exception as e:
                logger.warning(f"Failed to blacklist {addr}: {e}")

        # 2. Disable trading initially (if supported)
        try:
            tx = await deployer.set_trading_enabled(contract_address, False)
            results["tx_hashes"].append(tx)
            results["trading_delayed"] = True
            logger.info(f"Anti-sniper: trading disabled, will enable after {trading_delay_blocks} blocks")
        except Exception as e:
            logger.warning(f"Trading disable not supported or failed: {e}")

        # 3. Set max wallet limit (if percent provided)
        if max_wallet_percent > 0:
            try:
                total_supply = int(deployment.total_supply)
                max_wallet = str(int(total_supply * max_wallet_percent / 100))
                tx = await deployer.set_max_wallet(contract_address, max_wallet)
                results["tx_hashes"].append(tx)
                results["max_wallet_set"] = True
                logger.info(f"Anti-sniper: max wallet set to {max_wallet_percent}% ({max_wallet})")
            except Exception as e:
                logger.warning(f"Max wallet set failed: {e}")

        # 4. Set max tx limit (if percent provided)
        if max_tx_percent > 0:
            try:
                total_supply = int(deployment.total_supply)
                max_tx = str(int(total_supply * max_tx_percent / 100))
                tx = await deployer.set_max_tx(contract_address, max_tx)
                results["tx_hashes"].append(tx)
                results["max_tx_set"] = True
                logger.info(f"Anti-sniper: max tx set to {max_tx_percent}% ({max_tx})")
            except Exception as e:
                logger.warning(f"Max tx set failed: {e}")

        return results

    @classmethod
    async def enable_trading_after_delay(
        cls,
        deployer: Any,
        contract_address: str,
        delay_blocks: int,
        current_block: int,
    ) -> str:
        """Enable trading after block delay."""
        target_block = current_block + delay_blocks
        logger.info(f"Anti-sniper: trading will enable at block {target_block}")

        # This would be called by a cron job or background task
        # For now, return the target block
        return str(target_block)


# ── Snapshot Engine ─────────────────────────────────────────────


class SnapshotEngine:
    """
    Create snapshots of token holders for airdrop eligibility.
    Supports EVM chains (via RPC) and Solana (via RPC).
    """

    @staticmethod
    async def create_evm_snapshot(
        token_address: str,
        chain: str,
        rpc_url: str,
        min_holdings: str = "0",
        excluded_addresses: list[str] | None = None,
        block_number: int | None = None,
    ) -> AirdropSnapshot:
        """Create snapshot of EVM token holders."""
        from web3 import Web3

        w3 = Web3(Web3.HTTPProvider(rpc_url))

        # Standard ERC-20 events/topics
        transfer_topic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"

        # Get current block if not specified
        if block_number is None:
            block_number = w3.eth.block_number

        # Query Transfer events to find all holders
        # This is a simplified approach — for production, use a subgraph or indexer
        logs = w3.eth.get_logs(
            {
                "fromBlock": max(0, block_number - 100000),  # Last ~100k blocks
                "toBlock": block_number,
                "address": token_address,
                "topics": [transfer_topic],
            }
        )

        # Build holder balances from transfer logs
        balances: dict[str, int] = {}
        for log in logs:
            try:
                from_addr = "0x" + log.topics[1].hex()[-40:]
                to_addr = "0x" + log.topics[2].hex()[-40:]
                amount = int(log.data.hex(), 16) if log.data else 0

                balances[from_addr] = balances.get(from_addr, 0) - amount
                balances[to_addr] = balances.get(to_addr, 0) + amount
            except Exception:
                continue

        # Filter for positive balances above minimum
        min_amount = int(min_holdings)
        excluded = set(excluded_addresses or [])

        holders = []
        total_supply = 0
        for addr, balance in balances.items():
            if balance > 0 and balance >= min_amount and addr.lower() not in excluded:
                holders.append(
                    AirdropRecipient(
                        address=addr,
                        amount=str(balance),
                        reason="snapshot_holder",
                    )
                )
                total_supply += balance

        snapshot = AirdropSnapshot(
            snapshot_id=f"snap_{chain}_{token_address}_{block_number}_{int(time.time())}",
            source_token=token_address,
            source_chain=chain,
            block_number=block_number,
            holders=holders,
            total_holders=len(holders),
            total_supply_snapshotted=str(total_supply),
            excluded_addresses=list(excluded),
            min_holdings=min_holdings,
        )

        logger.info(f"Snapshot created: {snapshot.snapshot_id} — {len(holders)} holders, {total_supply} total")
        return snapshot

    @staticmethod
    async def create_solana_snapshot(
        token_address: str,
        rpc_url: str,
        min_holdings: str = "0",
        excluded_addresses: list[str] | None = None,
    ) -> AirdropSnapshot:
        """Create snapshot of SPL token holders."""
        from solana.rpc.api import Client
        from solders.pubkey import Pubkey

        client = Client(rpc_url)
        mint = Pubkey.from_string(token_address)

        # Get all token accounts for this mint
        response = client.get_token_accounts_by_mint_json_parsed(mint, commitment="confirmed")

        holders = []
        total_supply = 0
        excluded = set(excluded_addresses or [])
        min_amount = int(min_holdings)

        for account in response["result"]["value"]:
            try:
                parsed = account["account"]["data"]["parsed"]["info"]
                owner = parsed["owner"]
                amount = int(parsed["tokenAmount"]["amount"])

                if amount >= min_amount and owner not in excluded:
                    holders.append(
                        AirdropRecipient(
                            address=owner,
                            amount=str(amount),
                            reason="snapshot_holder",
                        )
                    )
                    total_supply += amount
            except Exception:
                continue

        snapshot = AirdropSnapshot(
            snapshot_id=f"snap_solana_{token_address}_{int(time.time())}",
            source_token=token_address,
            source_chain="solana",
            holders=holders,
            total_holders=len(holders),
            total_supply_snapshotted=str(total_supply),
            excluded_addresses=list(excluded),
            min_holdings=min_holdings,
        )

        return snapshot


# ── Airdrop Distributor ───────────────────────────────────────


class AirdropDistributor:
    """
    Execute airdrop distributions across chains.
    Supports batch transfers for gas efficiency.
    """

    @staticmethod
    async def execute_evm_airdrop(
        deployer: Any,
        contract_address: str,
        recipients: list[AirdropRecipient],
        batch_size: int = 50,
    ) -> dict[str, Any]:
        """Execute airdrop on EVM chain (batch or individual)."""
        results = {
            "total_recipients": len(recipients),
            "successful": 0,
            "failed": 0,
            "tx_hashes": [],
            "errors": [],
        }

        # For small airdrops, use individual transfers
        # For large ones, use a Merkle distributor or batch contract
        if len(recipients) <= batch_size:
            # Individual transfers
            for recipient in recipients:
                try:
                    tx = await deployer.mint_tokens(
                        contract_address,
                        recipient.address,
                        recipient.amount,
                    )
                    recipient.claimed = True
                    recipient.claim_tx = tx
                    recipient.claimed_at = datetime.utcnow().isoformat()
                    results["successful"] += 1
                    results["tx_hashes"].append(tx)
                    logger.info(f"Airdropped {recipient.amount} to {recipient.address}")
                except Exception as e:
                    results["failed"] += 1
                    results["errors"].append({"address": recipient.address, "error": str(e)})
                    logger.error(f"Airdrop failed for {recipient.address}: {e}")
        else:
            # Batch via multicall or distributor contract
            # For now, chunk into batches
            for i in range(0, len(recipients), batch_size):
                batch = recipients[i : i + batch_size]
                try:
                    # Use a batch mint function if available
                    tx = await AirdropDistributor._batch_mint_evm(deployer, contract_address, batch)
                    for r in batch:
                        r.claimed = True
                        r.claim_tx = tx
                        r.claimed_at = datetime.utcnow().isoformat()
                    results["successful"] += len(batch)
                    results["tx_hashes"].append(tx)
                except Exception as e:
                    results["failed"] += len(batch)
                    logger.error(f"Batch airdrop failed: {e}")

        return results

    @staticmethod
    async def _batch_mint_evm(
        deployer: Any,
        contract_address: str,
        recipients: list[AirdropRecipient],
    ) -> str:
        """Batch mint via multicall or custom contract."""
        # This would use a deployed MerkleDistributor or Multicall contract
        # For now, return a placeholder
        logger.info(f"Batch mint of {len(recipients)} recipients")
        return "batch_tx_placeholder"

    @staticmethod
    async def execute_solana_airdrop(
        deployer: Any,
        contract_address: str,
        recipients: list[AirdropRecipient],
    ) -> dict[str, Any]:
        """Execute airdrop on Solana."""
        results = {
            "total_recipients": len(recipients),
            "successful": 0,
            "failed": 0,
            "tx_hashes": [],
        }

        for recipient in recipients:
            try:
                tx = await deployer.mint_tokens(
                    contract_address,
                    recipient.address,
                    recipient.amount,
                )
                recipient.claimed = True
                recipient.claim_tx = tx
                results["successful"] += 1
                results["tx_hashes"].append(tx)
            except Exception as e:
                results["failed"] += 1
                logger.error(f"Solana airdrop failed for {recipient.address}: {e}")

        return results


# ── Team Allocation Engine ────────────────────────────────────


class TeamAllocation:
    """
    Allocate tokens to team, dev, marketing, treasury with vesting.

    Typical allocation:
      • Development: 15-20%
      • Marketing: 5-10%
      • Treasury/DAO: 10-15%
      • Advisors: 5-10%
      • Airdrop: 10-20%
      • Liquidity: 20-30%
      • Public sale: remaining
    """

    @staticmethod
    async def allocate_team_tokens(
        deployer: Any,
        deployment: TokenDeployment,
        allocations: list[dict[str, Any]],
    ) -> dict[str, Any]:
        """
        Allocate team/dev tokens with optional immediate + vested portions.

        allocations: [
            {"address": "0x...", "role": "dev", "percent": 10, "immediate": 20, "vested": 80, "cliff_months": 6, "vesting_months": 24},
            {"address": "0x...", "role": "marketing", "percent": 5, "immediate": 50, "vested": 50, "cliff_months": 3, "vesting_months": 12},
        ]
        """
        results = {
            "allocations": [],
            "total_allocated": 0,
            "tx_hashes": [],
        }

        total_supply = int(deployment.total_supply)

        for alloc in allocations:
            try:
                percent = alloc["percent"]
                amount = int(total_supply * percent / 100)
                immediate_percent = alloc.get("immediate", 0)
                immediate_amount = int(amount * immediate_percent / 100)
                vested_amount = amount - immediate_amount

                # Mint immediate portion
                if immediate_amount > 0:
                    tx = await deployer.mint_tokens(
                        deployment.contract_address,
                        alloc["address"],
                        str(immediate_amount),
                    )
                    results["tx_hashes"].append(tx)

                # Set up vesting for remainder
                if vested_amount > 0:
                    vesting = VestingSchedule(
                        schedule_id=f"vest_{deployment.deployment_id}_{alloc['role']}_{int(time.time())}",
                        deployment_id=deployment.deployment_id,
                        beneficiary=alloc["address"],
                        total_amount=str(vested_amount),
                        start_date=datetime.utcnow().isoformat(),
                        cliff_months=alloc.get("cliff_months", 0),
                        vesting_months=alloc.get("vesting_months", 0),
                        monthly_release=str(vested_amount // max(alloc.get("vesting_months", 1), 1)),
                    )
                    # Store vesting schedule
                    await AirdropStorage.save_vesting(vesting)

                results["allocations"].append(
                    {
                        "role": alloc["role"],
                        "address": alloc["address"],
                        "percent": percent,
                        "total_amount": str(amount),
                        "immediate": str(immediate_amount),
                        "vested": str(vested_amount),
                        "tx_hashes": results["tx_hashes"][-1:],
                    }
                )
                results["total_allocated"] += amount

            except Exception as e:
                logger.error(f"Team allocation failed for {alloc}: {e}")

        return results


# ── Storage ─────────────────────────────────────────────────────


class AirdropStorage:
    """Store airdrop snapshots, campaigns, and vesting schedules."""

    @staticmethod
    async def save_snapshot(snapshot: AirdropSnapshot) -> bool:
        """Save snapshot to Redis/Supabase."""
        try:
            from app.token_deployer import get_storage

            storage = await get_storage()

            if storage.redis:
                key = f"airdrop_snapshot:{snapshot.snapshot_id}"
                await storage.redis.set(key, json.dumps(snapshot.__dict__))
                await storage.redis.sadd("airdrop_snapshots:all", snapshot.snapshot_id)

            if storage.supabase:
                storage.supabase.table("airdrop_snapshots").upsert(
                    {
                        "snapshot_id": snapshot.snapshot_id,
                        "source_token": snapshot.source_token,
                        "source_chain": snapshot.source_chain,
                        "block_number": snapshot.block_number,
                        "timestamp": snapshot.timestamp,
                        "holders": [h.__dict__ for h in snapshot.holders],
                        "total_holders": snapshot.total_holders,
                        "total_supply": snapshot.total_supply_snapshotted,
                    }
                ).execute()

            return True
        except Exception as e:
            logger.error(f"Save snapshot failed: {e}")
            return False

    @staticmethod
    async def save_campaign(campaign: AirdropCampaign) -> bool:
        """Save campaign to storage."""
        try:
            from app.token_deployer import get_storage

            storage = await get_storage()

            if storage.redis:
                key = f"airdrop_campaign:{campaign.campaign_id}"
                await storage.redis.set(key, json.dumps(campaign.to_dict()))
                await storage.redis.sadd("airdrop_campaigns:all", campaign.campaign_id)

            return True
        except Exception as e:
            logger.error(f"Save campaign failed: {e}")
            return False

    @staticmethod
    async def save_vesting(vesting: VestingSchedule) -> bool:
        """Save vesting schedule."""
        try:
            from app.token_deployer import get_storage

            storage = await get_storage()

            if storage.redis:
                key = f"vesting:{vesting.schedule_id}"
                await storage.redis.set(key, json.dumps(vesting.__dict__))
                await storage.redis.sadd("vesting:all", vesting.schedule_id)

            return True
        except Exception as e:
            logger.error(f"Save vesting failed: {e}")
            return False

    @staticmethod
    async def get_campaign(campaign_id: str) -> AirdropCampaign | None:
        """Get campaign by ID."""
        try:
            from app.token_deployer import get_storage

            storage = await get_storage()

            if storage.redis:
                data = await storage.redis.get(f"airdrop_campaign:{campaign_id}")
                if data:
                    d = json.loads(data)
                    return AirdropCampaign(**d)

            return None
        except Exception as e:
            logger.error(f"Get campaign failed: {e}")
            return None


# ── Convenience Functions ─────────────────────────────────────


async def create_full_token_with_protection(
    chain: str,
    name: str,
    symbol: str,
    decimals: int,
    initial_supply: str,
    team_allocations: list[dict[str, Any]] | None = None,
    airdrop_source_token: str | None = None,
    airdrop_source_chain: str | None = None,
    anti_sniper: bool = True,
    trading_delay_blocks: int = 0,
    max_wallet_percent: float = 2.0,
    max_tx_percent: float = 1.0,
    blacklist_addresses: list[str] | None = None,
) -> dict[str, Any]:
    """
    Full token launch with team allocation, airdrop, and anti-sniper protection.

    This is the main function for launching CRM v2 or any new token.
    """
    from app.token_deployer import DeployParams

    results = {
        "deployment": None,
        "anti_sniper": None,
        "team_allocation": None,
        "airdrop": None,
        "errors": [],
    }

    try:
        # 1. Deploy token
        deployer = TokenDeployerFactory.get_deployer(chain)
        params = DeployParams(
            chain=chain,
            name=name,
            symbol=symbol,
            decimals=decimals,
            initial_supply=initial_supply,
            mintable=True,
            burnable=True,
            blacklist_enabled=anti_sniper,
            trading_enabled=not anti_sniper,  # Disabled initially if anti-sniper
        )

        deployment = await deployer.deploy_token(params)
        results["deployment"] = deployment.to_dict()

        # Save deployment
        storage = await get_storage()
        await storage.save(deployment)

        # 2. Apply anti-sniper protection
        if anti_sniper:
            protection = await AntiSniperProtection.apply_protection(
                deployer,
                deployment.contract_address,
                deployment,
                blacklist_addresses=blacklist_addresses,
                trading_delay_blocks=trading_delay_blocks,
                max_wallet_percent=max_wallet_percent,
                max_tx_percent=max_tx_percent,
            )
            results["anti_sniper"] = protection

        # 3. Team allocation
        if team_allocations:
            team_result = await TeamAllocation.allocate_team_tokens(deployer, deployment, team_allocations)
            results["team_allocation"] = team_result

        # 4. Airdrop from snapshot
        if airdrop_source_token and airdrop_source_chain:
            # Create snapshot
            if airdrop_source_chain in ["ethereum", "base", "bsc"]:
                rpc = os.getenv(f"{airdrop_source_chain.upper()}_RPC_URL", "")
                snapshot = await SnapshotEngine.create_evm_snapshot(
                    airdrop_source_token,
                    airdrop_source_chain,
                    rpc,
                )
            elif airdrop_source_chain == "solana":
                rpc = os.getenv("SOLANA_RPC_URL", "")
                snapshot = await SnapshotEngine.create_solana_snapshot(
                    airdrop_source_token,
                    rpc,
                )
            else:
                raise ValueError(f"Unsupported snapshot chain: {airdrop_source_chain}")

            await AirdropStorage.save_snapshot(snapshot)

            # Execute airdrop
            if chain in ["ethereum", "base", "bsc"]:
                airdrop_result = await AirdropDistributor.execute_evm_airdrop(
                    deployer, deployment.contract_address, snapshot.holders
                )
            elif chain == "solana":
                airdrop_result = await AirdropDistributor.execute_solana_airdrop(
                    deployer, deployment.contract_address, snapshot.holders
                )
            else:
                airdrop_result = {"error": "Airdrop not supported for this chain yet"}

            results["airdrop"] = {
                "snapshot_id": snapshot.snapshot_id,
                "holders": len(snapshot.holders),
                "result": airdrop_result,
            }

        return results

    except Exception as e:
        logger.error(f"Full token launch failed: {e}")
        results["errors"].append(str(e))
        raise
