"""Cloud sync — auth, evidence packets, decisions."""

from __future__ import annotations

import json
import os
import secrets
import threading
import time
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime
from typing import Callable

from superpitch.app_paths import log_sync_error

WORKER_URL = os.environ.get("SUPERPITCH_WORKER_URL", "https://superpitch.eyoel-lundberg.workers.dev")
WEB_URL    = os.environ.get("SUPERPITCH_WEB_URL",    "https://superpitch.dev")

POLL_INTERVAL  = 2 * 60   # 2 minutes between background polls
_AUTH_INTERVAL = 3.0
_AUTH_TIMEOUT  = 120


class SessionExpiredError(PermissionError):
    pass


class SubscriptionRequiredError(PermissionError):
    pass


def _request(
    method: str,
    path: str,
    *,
    body: dict | None = None,
    token: str | None = None,
    timeout: int = 15,
) -> tuple[dict, int]:
    url = f"{WORKER_URL}{path}"
    headers: dict[str, str] = {"Content-Type": "application/json", "User-Agent": "Superpitch-CLI/1.0"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(url, data=data, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            return json.loads(resp.read()), resp.status
    except urllib.error.HTTPError as exc:
        try:
            return json.loads(exc.read()), exc.code
        except Exception:
            return {"error": str(exc)}, exc.code
    except Exception as exc:
        return {"error": str(exc)}, 0


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

def start_cli_auth() -> str:
    cli_token = f"sp-{secrets.token_urlsafe(20).lower()}"
    data, status = _request("POST", "/auth/cli/init", body={"token": cli_token})
    if status != 200 or not data.get("ok"):
        raise RuntimeError(data.get("error", "failed to start auth"))
    import webbrowser
    webbrowser.open(f"{WEB_URL}/auth/cli?token={urllib.parse.quote(cli_token)}")
    return cli_token


def poll_cli_auth(
    cli_token: str,
    on_tick: Callable[[int], None] | None = None,
) -> dict | None:
    start    = time.time()
    deadline = start + _AUTH_TIMEOUT
    while time.time() < deadline:
        time.sleep(_AUTH_INTERVAL)
        if on_tick:
            on_tick(int(time.time() - start))
        data, status = _request("GET", f"/auth/cli/poll?token={urllib.parse.quote(cli_token)}")
        if status == 200 and data.get("ready"):
            return data
    return None


def fetch_api_key(session_token: str) -> str | None:
    data, status = _request("GET", "/api-key", token=session_token)
    if status == 200:
        return data.get("api_key")
    return None


# ── Thesis ────────────────────────────────────────────────────────────────────

def set_thesis(session_token: str, thesis: str) -> bool:
    data, status = _request("POST", "/thesis", body={"thesis": thesis}, token=session_token)
    return status == 200 and bool(data.get("saved"))


def get_receive_address(session_token: str) -> str | None:
    data, status = _request("GET", "/setup", token=session_token)
    if status == 200:
        return data.get("receive_address")
    return None


# ── Profile ───────────────────────────────────────────────────────────────────

def set_profile(session_token: str, display_name: str) -> bool:
    data, status = _request("POST", "/profile", body={"display_name": display_name}, token=session_token)
    return status == 200 and bool(data.get("saved"))


# ── Handle ────────────────────────────────────────────────────────────────────

def check_handle(handle: str) -> bool:
    """Returns True if the handle is available."""
    data, status = _request("GET", f"/handle/check?handle={urllib.parse.quote(handle)}")
    return status == 200 and bool(data.get("available"))


def set_handle(session_token: str, handle: str) -> bool:
    data, status = _request("POST", "/handle", body={"handle": handle}, token=session_token)
    return status == 200 and bool(data.get("saved"))


# ── Evidence packet sync ─────────────────────────────────────────────────────

def _sync_pitches(session_token: str, since: str | None = None) -> list[dict]:
    path = "/pitches/sync"
    if since:
        path += f"?since={urllib.parse.quote(since)}"
    data, status = _request("GET", path, token=session_token)
    if status == 401:
        raise SessionExpiredError()
    if status == 402:
        raise SubscriptionRequiredError()
    if status != 200:
        return []
    return data.get("pitches", [])


def process_email_queue(
    config: dict,
    on_processed: Callable[[int], None] | None = None,
) -> int:
    """Pull finished evidence packets from the Worker and save locally."""
    from superpitch.state import save_evidence_entry

    session_token = config.get("session_token", "")
    if not session_token:
        return 0

    try:
        pitches = _sync_pitches(session_token, since=config.get("last_synced_at"))
    except (SessionExpiredError, SubscriptionRequiredError):
        raise
    except Exception as exc:
        log_sync_error("sync_pitches", exc)
        return 0

    config["last_synced_at"] = datetime.now().isoformat()

    added = 0
    for packet in pitches:
        email_id = packet.get("id", "")
        if not email_id:
            continue
        save_evidence_entry(packet, email_id, source_label=f"email:{packet.get('founder_email', '')}")
        added += 1

    if added and on_processed:
        on_processed(added)

    return added


# ── Decisions ─────────────────────────────────────────────────────────────────

def push_decision(session_token: str, pitch_id: str, decision: str) -> bool:
    data, status = _request(
        "POST", "/decisions",
        body={"pitch_id": pitch_id, "decision": decision},
        token=session_token,
    )
    return status == 200 and bool(data.get("saved"))


# ── Background poller ─────────────────────────────────────────────────────────

class SyncPoller:
    """Polls for new evidence packets every POLL_INTERVAL seconds in a daemon thread."""

    def __init__(self, config: dict, on_synced: Callable[[int], None] | None = None):
        self._config    = config
        self._on_synced = on_synced
        self._stop      = threading.Event()
        self._thread    = threading.Thread(target=self._run, daemon=True)

    def start(self) -> None:
        self._thread.start()

    def stop(self) -> None:
        self._stop.set()

    def _run(self) -> None:
        self._poll()  # immediate on start — no waiting for first results
        while not self._stop.is_set():
            if self._stop.wait(POLL_INTERVAL):
                break
            self._poll()

    def _poll(self) -> None:
        if not self._config.get("signed_in") or not self._config.get("session_token"):
            return
        if not self._config.get("anthropic_api_key"):
            return
        try:
            process_email_queue(self._config, on_processed=self._on_synced)
            from superpitch.state import save_config
            save_config(self._config)
        except Exception as exc:
            log_sync_error("background_poll", exc)
