# litcoin/miner.py
"""LITCOIN Miner — 3-line mining for AI agents."""

import time
import uuid
import logging
import requests

from .config import COORDINATOR_URL, CLAIMS_CONTRACT, FAUCET_CONTRACT, CHAIN_ID
from .wallet import create_wallet
from .solver import solve

logger = logging.getLogger("litcoin")


class Miner:
    """
    LITCOIN proof-of-comprehension miner.

    Usage:
        from litcoin import Miner

        miner = Miner(wallet_key="bk_YOUR_BANKR_KEY")
        miner.mine()  # starts mining (blocking)

    Or step by step:
        miner = Miner(wallet_key="0xYOUR_PRIVATE_KEY")
        miner.bootstrap()   # faucet if needed
        miner.mine(rounds=10)
        miner.claim()        # claim on-chain (Bankr only)
    """

    def __init__(
        self,
        wallet_key: str,
        ai_key: str = None,
        coordinator_url: str = None,
        auto_bootstrap: bool = True,
        verbose: bool = True,
    ):
        """
        Args:
            wallet_key: Bankr API key (bk_...) or Ethereum private key (0x...)
            ai_key: AI API key (not needed for current challenges)
            coordinator_url: Override coordinator URL
            auto_bootstrap: Auto-claim from faucet if balance is low
            verbose: Print mining progress
        """
        self.wallet = create_wallet(wallet_key)
        self.ai_key = ai_key
        self.base_url = coordinator_url or COORDINATOR_URL
        self.auto_bootstrap = auto_bootstrap
        self.verbose = verbose

        # Auth state
        self._token = None
        self._token_expiry = 0

        # Stats
        self.rounds = 0
        self.passes = 0
        self.fails = 0
        self.total_earned = 0

        if self.verbose:
            logging.basicConfig(
                level=logging.INFO,
                format="%(asctime)s [LITCOIN] %(message)s",
                datefmt="%H:%M:%S",
            )

        logger.info(f"Wallet: {self.wallet.address} ({self.wallet.wallet_type})")

    # ─── Auth ────────────────────────────────────────────────────────

    def _authenticate(self, force=False):
        """Get or refresh JWT token."""
        if not force and self._token and time.time() < (self._token_expiry - 60):
            return self._token

        logger.info("Authenticating...")

        # Get nonce
        r = requests.post(
            f"{self.base_url}/v1/auth/nonce",
            json={"miner": self.wallet.address},
            timeout=15,
        )
        r.raise_for_status()
        message = r.json()["message"]

        # Sign
        signature = self.wallet.sign(message)

        # Verify
        r = requests.post(
            f"{self.base_url}/v1/auth/verify",
            json={
                "miner": self.wallet.address,
                "message": message,
                "signature": signature,
            },
            timeout=15,
        )
        r.raise_for_status()
        data = r.json()
        self._token = data["token"]
        self._token_expiry = time.time() + data.get("expiresIn", 3600)
        logger.info("Authenticated ✓")
        return self._token

    def _auth_header(self):
        token = self._authenticate()
        return {"Authorization": f"Bearer {token}"}

    # ─── Balance ─────────────────────────────────────────────────────

    def balance(self) -> int:
        """Check LITCOIN balance (in whole tokens)."""
        r = requests.get(
            f"{self.base_url}/v1/claims/status",
            params={"wallet": self.wallet.address},
            timeout=15,
        )
        if r.status_code == 200:
            data = r.json()
            return data.get("claimable", 0)
        return 0

    def earnings(self) -> dict:
        """Check mining earnings and claim status."""
        r = requests.get(
            f"{self.base_url}/v1/claims/status",
            params={"wallet": self.wallet.address},
            timeout=15,
        )
        r.raise_for_status()
        return r.json()

    # ─── Faucet ──────────────────────────────────────────────────────

    def faucet(self) -> dict:
        """
        Claim tokens from the bootstrap faucet.
        Solves a trial challenge to prove AI capability, then claims on-chain.
        Returns dict with result info.
        """
        logger.info("Checking faucet eligibility...")

        # Check eligibility
        r = requests.get(
            f"{self.base_url}/v1/faucet/eligible",
            params={"wallet": self.wallet.address},
            timeout=15,
        )
        r.raise_for_status()
        eligibility = r.json()

        if not eligibility.get("eligible"):
            logger.info(f"Not eligible: {eligibility.get('reason')}")
            return {"success": False, "reason": eligibility.get("reason")}

        if eligibility.get("hasEnoughToMine"):
            logger.info("Already has enough tokens to mine")
            return {"success": False, "reason": "balance sufficient"}

        # Request trial challenge
        logger.info("Requesting trial challenge...")
        r = requests.post(
            f"{self.base_url}/v1/faucet/trial",
            json={"wallet": self.wallet.address},
            timeout=30,
        )
        r.raise_for_status()
        trial = r.json()

        # Solve trial
        logger.info("Solving trial challenge...")
        artifact = solve(
            trial.get("doc", ""),
            trial.get("questions", []),
            trial.get("constraints", []),
            trial.get("entities", []),
            trial.get("solveInstructions"),
        )

        if not artifact:
            logger.error("Failed to solve trial challenge")
            return {"success": False, "reason": "trial solve failed"}

        # Submit trial solution — coordinator auto-claims on-chain
        logger.info("Submitting trial solution...")
        r = requests.post(
            f"{self.base_url}/v1/faucet/verify",
            json={
                "wallet": self.wallet.address,
                "trialNonce": trial["trialNonce"],
                "artifact": artifact,
            },
            timeout=30,
        )
        r.raise_for_status()
        result = r.json()

        if not result.get("pass"):
            logger.error("Trial verification failed")
            return {"success": False, "reason": "trial verification failed"}

        tx_hash = result.get("txHash")
        if tx_hash:
            logger.info(f"Faucet claimed! Tx: https://basescan.org/tx/{tx_hash}")
            return {"success": True, "txHash": tx_hash, **result}
        else:
            logger.warning(f"Faucet signed but auto-submit failed: {result.get('txError')}")
            logger.info("Manual claim data available in response")
            return {"success": True, "pending_manual": True, **result}

    # ─── Bootstrap ───────────────────────────────────────────────────

    def bootstrap(self):
        """Check balance and claim from faucet if needed."""
        logger.info("Checking if bootstrap needed...")
        r = requests.get(
            f"{self.base_url}/v1/faucet/eligible",
            params={"wallet": self.wallet.address},
            timeout=15,
        )
        if r.status_code == 200:
            data = r.json()
            if data.get("eligible") and not data.get("hasEnoughToMine"):
                logger.info("Balance too low — claiming from faucet...")
                return self.faucet()
            elif data.get("hasEnoughToMine"):
                logger.info(f"Balance sufficient ({data.get('currentBalance', '?')} LITCOIN)")
            else:
                logger.info(f"Faucet already claimed: {data.get('reason')}")
        return None

    # ─── Mine ────────────────────────────────────────────────────────

    def mine(self, rounds: int = 0, max_failures: int = 5):
        """
        Start mining loop.

        Args:
            rounds: Number of rounds (0 = mine forever)
            max_failures: Stop after N consecutive solve failures
        """
        logger.info("Starting LITCOIN miner...")

        if self.auto_bootstrap:
            self.bootstrap()

        self._authenticate()

        consecutive_fails = 0
        infra_fails = 0

        while True:
            if rounds > 0 and self.rounds >= rounds:
                logger.info(f"Completed {rounds} rounds")
                break
            if consecutive_fails >= max_failures:
                logger.error(f"{max_failures} consecutive failures — stopping")
                break

            nonce = uuid.uuid4().hex[:32]
            logger.info(f"Round {self.rounds + 1}")

            # Get challenge
            try:
                r = requests.get(
                    f"{self.base_url}/v1/challenge",
                    params={"nonce": nonce},
                    headers=self._auth_header(),
                    timeout=30,
                )
            except Exception as e:
                infra_fails += 1
                wait = min(30 * infra_fails, 300)
                logger.warning(f"Network error: {e} — retry in {wait}s")
                time.sleep(wait)
                continue

            if r.status_code == 401:
                self._authenticate(force=True)
                continue
            if r.status_code == 429:
                logger.warning("Rate limited — waiting 30s")
                time.sleep(30)
                continue
            if r.status_code == 403:
                infra_fails += 1
                wait = min(30 * infra_fails, 300)
                logger.warning(f"Balance check failed — retry in {wait}s")
                time.sleep(wait)
                continue
            if r.status_code >= 500:
                infra_fails += 1
                wait = min(30 * infra_fails, 300)
                logger.warning(f"Server error {r.status_code} — retry in {wait}s")
                time.sleep(wait)
                continue
            if r.status_code != 200:
                logger.error(f"Challenge error: {r.status_code}")
                consecutive_fails += 1
                time.sleep(5)
                continue

            infra_fails = 0
            ch = r.json()
            logger.info(
                f"  Epoch {ch.get('epochId')} | "
                f"~{ch.get('rewardPerSolve', 0):,} LIT/solve | "
                f"Boost {ch.get('boostMultiplier', '1.00')}x"
            )

            # Solve
            artifact = solve(
                ch.get("doc", ""),
                ch.get("questions", []),
                ch.get("constraints", []),
                ch.get("entities", []),
                ch.get("solveInstructions"),
            )
            if not artifact:
                consecutive_fails += 1
                logger.error("Solve failed")
                continue

            # Submit
            try:
                r = requests.post(
                    f"{self.base_url}/v1/submit",
                    headers={
                        "Content-Type": "application/json",
                        **self._auth_header(),
                    },
                    json={
                        "challengeId": ch["challengeId"],
                        "artifact": artifact,
                        "nonce": nonce,
                    },
                    timeout=30,
                )
            except Exception as e:
                infra_fails += 1
                logger.warning(f"Submit network error: {e}")
                time.sleep(30)
                continue

            if r.status_code == 401:
                self._authenticate(force=True)
                continue
            if r.status_code not in (200, 201):
                logger.error(f"Submit error: {r.status_code}")
                consecutive_fails += 1
                continue

            result = r.json()
            self.rounds += 1

            if not result.get("pass"):
                self.fails += 1
                consecutive_fails += 1
                logger.error(f"FAILED — constraints: {result.get('failedConstraintIndices', [])}")
                time.sleep(2)
                continue

            # Success
            consecutive_fails = 0
            self.passes += 1
            reward = result.get("reward", 0)
            self.total_earned += reward
            logger.info(f"  ✅ PASS | +{reward:,} LIT | Total: {self.total_earned:,}")
            time.sleep(2)

        self._print_summary()

    def _print_summary(self):
        rate = f"{self.passes}/{self.rounds}" if self.rounds > 0 else "0/0"
        logger.info(f"\n{'='*50}")
        logger.info(f"Mining summary: {rate} pass rate")
        logger.info(f"Total earned: {self.total_earned:,} LITCOIN")
        logger.info(f"{'='*50}")

    # ─── Claim ───────────────────────────────────────────────────────

    def claim(self) -> dict:
        """
        Claim mining rewards on-chain.
        Bankr wallets: submits tx automatically.
        EOA wallets: returns calldata for manual submission.
        """
        # Get claim signature from coordinator
        r = requests.post(
            f"{self.base_url}/v1/claims/sign",
            json={"wallet": self.wallet.address},
            timeout=15,
        )
        r.raise_for_status()
        data = r.json()

        if data.get("claimable", 0) == 0:
            logger.info("Nothing to claim")
            return {"success": False, "reason": "nothing claimable"}

        logger.info(f"Claiming {data['claimable']:,} LITCOIN...")

        total_earned_wei = data["totalEarnedWei"]
        signature = data["signature"]

        # Encode claim(uint256, bytes)
        selector = "38926b6d"
        amount_hex = hex(int(total_earned_wei))[2:].zfill(64)
        offset = "0000000000000000000000000000000000000000000000000000000000000040"
        sig_hex = signature[2:] if signature.startswith("0x") else signature
        sig_len = len(sig_hex) // 2
        sig_len_hex = hex(sig_len)[2:].zfill(64)
        padded_sig = sig_hex + "0" * (64 - (len(sig_hex) % 64)) if len(sig_hex) % 64 != 0 else sig_hex

        calldata = f"0x{selector}{amount_hex}{offset}{sig_len_hex}{padded_sig}"

        if self.wallet.wallet_type == "bankr":
            try:
                result = self.wallet.submit_tx(CLAIMS_CONTRACT, calldata)
                logger.info(f"Claim submitted: {result}")
                return {"success": True, "tx": result, **data}
            except Exception as e:
                logger.error(f"Claim tx failed: {e}")
                return {"success": False, "reason": str(e)}
        else:
            logger.info("EOA wallet — submit this transaction manually or via Dashboard:")
            logger.info(f"  To: {CLAIMS_CONTRACT}")
            logger.info(f"  Data: {calldata[:50]}...")
            return {"success": True, "pending_tx": True, "calldata": calldata, **data}
