"""run_loop() — one function that turns any Python script into a reporting agent.

Usage:
    import wzrd
    wzrd.run_loop()  # uses default keypair, runs forever

    # Or with options:
    wzrd.run_loop(
        keypair="~/.config/solana/id.json",
        tasks=["code", "chat", "reasoning"],
        cycle_seconds=300,
    )

The loop:
    1. Authenticate (Ed25519 challenge/verify)
    2. Pick top model per task via the live velocity signal
    3. Report picks for reward eligibility
    4. Check reward status + claim if available (gasless relay)
    5. Sleep + repeat

No SOL required for claims — the relay pays gas.
CCM rewards accrue when the backend marks them available.
"""

from __future__ import annotations

import logging
import signal
import sys
import time
from typing import Optional, Sequence

from .agent import WZRDAgent, load_keypair
from .client import PickResult, WZRDError, pick_details

logger = logging.getLogger("wzrd.loop")

# Defaults
DEFAULT_TASKS = ("code", "chat", "reasoning")
DEFAULT_CYCLE_SECONDS = 300  # 5 minutes
MIN_CYCLE_SECONDS = 30
SESSION_REFRESH_CYCLES = 100  # re-auth every ~8 hours at 5min cycles


def run_loop(
    keypair: "str | bytes | None" = None,
    *,
    tasks: Sequence[str] = DEFAULT_TASKS,
    cycle_seconds: int = DEFAULT_CYCLE_SECONDS,
    max_cycles: Optional[int] = None,
    claim: bool = True,
    quiet: bool = False,
) -> None:
    """Run the WZRD reward loop. Blocks forever (or for max_cycles).

    Args:
        keypair: Solana keypair — path, base58 string, bytes, or None (auto-detect).
        tasks: Task types to report on. Each task picks and reports one model per cycle.
        cycle_seconds: Seconds between cycles (min 30).
        max_cycles: Stop after N cycles. None = run forever.
        claim: If True, attempt gasless relay claim when CCM is claimable.
        quiet: If True, suppress info logging (errors still print).
    """
    if not quiet:
        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s [wzrd] %(message)s",
            datefmt="%H:%M:%S",
        )

    cycle_seconds = max(cycle_seconds, MIN_CYCLE_SECONDS)

    # Load keypair
    signer = load_keypair(keypair)
    pubkey = str(signer.pubkey())
    logger.info("agent: %s", pubkey)
    logger.info("tasks: %s | cycle: %ds", ", ".join(tasks), cycle_seconds)

    # Set up graceful shutdown
    _shutdown = False

    def _handle_signal(signum, frame):
        nonlocal _shutdown
        _shutdown = True
        logger.info("shutting down...")

    signal.signal(signal.SIGINT, _handle_signal)
    signal.signal(signal.SIGTERM, _handle_signal)

    agent = WZRDAgent()
    cycle = 0

    while not _shutdown:
        if max_cycles is not None and cycle >= max_cycles:
            logger.info("max_cycles reached (%d), stopping", max_cycles)
            break

        cycle += 1
        cycle_start = time.monotonic()

        try:
            # Re-auth periodically
            if cycle == 1 or cycle % SESSION_REFRESH_CYCLES == 0:
                session = agent.authenticate(keypair)
                logger.info("authenticated (session %d cycles)", SESSION_REFRESH_CYCLES)

            # Pick + report per task
            for task in tasks:
                try:
                    choice = pick_details(task)
                    if choice is None:
                        continue

                    result = agent.report_pick(
                        choice,
                        source="wzrd-client",
                        policy_version="1.0",
                        candidate_rank=0,
                        exploration=False,
                        run_id=pubkey[:12],
                        cycle_id=cycle,
                    )
                    ccm = result.get("pending_ccm", 0)
                    logger.info(
                        "cycle %d | %s → %s | pending: %s CCM",
                        cycle, task, choice.model,
                        f"{ccm / 1e9:,.0f}" if ccm else "0",
                    )
                except WZRDError as exc:
                    logger.warning("report %s failed: %s", task, exc)

            # Claim if available
            if claim:
                try:
                    earned = agent.earned()
                    pending = earned.get("pending_ccm", 0)
                    if pending > 0:
                        # Attempt relay claim
                        try:
                            claim_resp = agent._authorized_json(
                                f"/v1/claims/{pubkey}/relay",
                                method="POST",
                            )
                            tx = claim_resp.get("tx_sig", "?")
                            logger.info(
                                "claimed %s CCM | tx: %s",
                                f"{pending / 1e9:,.0f}",
                                tx[:20] + "..." if len(tx) > 20 else tx,
                            )
                        except WZRDError:
                            pass  # rate limited or relay unavailable — try next cycle
                except WZRDError:
                    pass  # earned check failed — try next cycle

        except WZRDError as exc:
            logger.error("cycle %d failed: %s", cycle, exc)
        except Exception as exc:
            logger.error("cycle %d unexpected error: %s", cycle, exc)

        # Sleep until next cycle
        elapsed = time.monotonic() - cycle_start
        sleep_time = max(0, cycle_seconds - elapsed)
        if sleep_time > 0 and not _shutdown:
            time.sleep(sleep_time)

    logger.info("stopped after %d cycles", cycle)
