"""Helpers for a real-worker end-to-end validation run."""

from __future__ import annotations

import json
import platform
import shlex
import sys
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any

import httpx

from hive_compute.ops.job_smoke import (
    DEFAULT_TEST_AUDIO_URL,
    JobSmokeResult,
    run_job_smoke_check,
    troubleshooting_hints,
)
from hive_compute.worker.config_model import WorkerConfig, load_worker_config_raw
from hive_compute.worker.diagnostics import DoctorCheck, run_doctor
from hive_compute.worker.idle_detector import get_hardware_info
from hive_compute.worker.keychain import describe_worker_token_storage, resolve_worker_token


@dataclass(frozen=True)
class LocalWorkerSnapshot:
    config_path: str
    worker_id: str
    coordinator_url: str
    token_storage: str
    handler_backend: str
    handler_model: str
    handler_quantization: str
    handler_language: str
    supported_models: list[str]
    machine_chip: str | None
    memory_gb: int | None
    platform_summary: str
    status_lines: list[str]
    doctor_checks: list[DoctorCheck]


@dataclass(frozen=True)
class ApiProbe:
    label: str
    ok: bool
    status_code: int | None = None
    payload: Any = None
    error: str | None = None

    def summary(self) -> str:
        if self.ok:
            return f"HTTP {self.status_code}"
        if self.error:
            return self.error
        if self.status_code is not None:
            return f"HTTP {self.status_code}"
        return "request failed"


@dataclass(frozen=True)
class RealWorkerValidationResult:
    executed_at: str
    command: str
    worker_command: str
    operator: str | None
    audio_url: str
    screenshot_paths: list[str]
    notes: list[str]
    local: LocalWorkerSnapshot
    smoke_result: JobSmokeResult
    job_status: ApiProbe | None = None
    job_result: ApiProbe | None = None
    job_events: ApiProbe | None = None
    worker_reputation: ApiProbe | None = None
    worker_payouts: ApiProbe | None = None
    billing_before: ApiProbe | None = None
    billing_after: ApiProbe | None = None
    follow_up_hints: list[str] = field(default_factory=list)

    @property
    def blocking_issues(self) -> list[str]:
        issues: list[str] = []
        doctor_failures = [check.summary for check in self.local.doctor_checks if check.status == "fail"]
        if doctor_failures:
            issues.append(f"Doctor reported failures: {'; '.join(doctor_failures)}")
        if not self.smoke_result.ok:
            issues.append(self.smoke_result.detail)
        for probe in (
            self.job_status,
            self.job_result,
            self.job_events,
            self.worker_reputation,
            self.worker_payouts,
        ):
            if probe is not None and not probe.ok:
                issues.append(f"{probe.label}: {probe.summary()}")
        return issues

    @property
    def overall_result(self) -> str:
        return "pass" if not self.blocking_issues else "fail"


def collect_local_worker_snapshot(
    config_path: str,
    *,
    doctor_smoke_backend: bool = False,
) -> LocalWorkerSnapshot:
    raw_config = load_worker_config_raw(config_path)
    config = WorkerConfig(**raw_config)
    transcribe = config.handlers["transcribe"]
    hardware = get_hardware_info()

    status_lines = [
        f"Worker ID:      {config.worker_id or 'unknown'}",
        f"Coordinator:    {config.coordinator_url or 'unknown'}",
        f"Token storage:  {describe_worker_token_storage(raw_config)}",
        f"Handlers:       {', '.join(config.handlers.keys())}",
        f"Idle minutes:   {config.idle_detection.min_idle_minutes}",
        f"Poll interval:  {config.polling.interval_seconds}s",
    ]

    return LocalWorkerSnapshot(
        config_path=str(Path(config_path).expanduser()),
        worker_id=config.worker_id,
        coordinator_url=config.coordinator_url.rstrip("/"),
        token_storage=describe_worker_token_storage(raw_config),
        handler_backend=transcribe.backend,
        handler_model=transcribe.model,
        handler_quantization=transcribe.quantization,
        handler_language=transcribe.language,
        supported_models=list(transcribe.supported_models),
        machine_chip=hardware.get("chip"),
        memory_gb=hardware.get("memory_gb"),
        platform_summary=_platform_summary(),
        status_lines=status_lines,
        doctor_checks=run_doctor(
            config_path,
            check_network=True,
            run_backend_smoke=doctor_smoke_backend,
        ),
    )


def run_real_worker_validation(
    *,
    base_url: str | None,
    client_api_key: str,
    config_path: str,
    operator: str | None = None,
    audio_url: str = DEFAULT_TEST_AUDIO_URL,
    language: str | None = None,
    model: str | None = None,
    audio_duration: float | None = None,
    timeout_seconds: int = 600,
    poll_interval_seconds: int = 5,
    expected_substring: str | None = None,
    doctor_smoke_backend: bool = False,
    screenshot_paths: list[str] | None = None,
    notes: list[str] | None = None,
    command: str | None = None,
    worker_command: str | None = None,
) -> RealWorkerValidationResult:
    local = collect_local_worker_snapshot(
        config_path,
        doctor_smoke_backend=doctor_smoke_backend,
    )
    resolved_base_url = (base_url or local.coordinator_url).rstrip("/")
    resolved_model = model or local.handler_model
    resolved_language = language or local.handler_language

    raw_config = load_worker_config_raw(config_path)
    worker_token_probe = _resolve_worker_token_probe(raw_config, local.worker_id)
    client_headers = {"Authorization": f"Bearer {client_api_key}"}

    with httpx.Client(base_url=resolved_base_url, timeout=15) as client:
        billing_before = _fetch_billing_balance(client, client_headers)
        smoke_result = run_job_smoke_check(
            resolved_base_url,
            client_api_key,
            audio_url=audio_url,
            language=resolved_language,
            model=resolved_model,
            audio_duration=audio_duration,
            timeout_seconds=timeout_seconds,
            poll_interval_seconds=poll_interval_seconds,
            expected_substring=expected_substring,
        )

        job_status = None
        job_result = None
        job_events = None
        if smoke_result.job_id:
            job_status = _fetch_json_probe(
                client,
                f"/api/v1/jobs/{smoke_result.job_id}",
                headers=client_headers,
                label="job status",
            )
            job_result = _fetch_json_probe(
                client,
                f"/api/v1/jobs/{smoke_result.job_id}/result",
                headers=client_headers,
                label="job result",
                success_codes={200, 202},
            )
            job_events = _fetch_json_probe(
                client,
                f"/api/v1/jobs/{smoke_result.job_id}/events",
                headers=client_headers,
                label="job events",
            )

        billing_after = _fetch_billing_balance(client, client_headers)

        if worker_token_probe.ok and isinstance(worker_token_probe.payload, str):
            worker_headers = {
                "Authorization": f"Bearer {worker_token_probe.payload}",
                "X-Worker-ID": local.worker_id,
            }
            worker_reputation = _fetch_json_probe(
                client,
                "/api/v1/workers/me/reputation",
                headers=worker_headers,
                label="worker reputation",
            )
            worker_payouts = _fetch_json_probe(
                client,
                "/api/v1/workers/me/payouts",
                headers=worker_headers,
                label="worker payouts",
            )
        else:
            worker_reputation = ApiProbe(
                label="worker reputation",
                ok=False,
                error=worker_token_probe.summary(),
            )
            worker_payouts = ApiProbe(
                label="worker payouts",
                ok=False,
                error=worker_token_probe.summary(),
            )

    hints = troubleshooting_hints(smoke_result)
    for probe in (job_status, job_result, job_events, worker_reputation, worker_payouts):
        if probe is not None and not probe.ok:
            hints.append(f"Inspect {probe.label}: {probe.summary()}")

    return RealWorkerValidationResult(
        executed_at=datetime.now().astimezone().isoformat(timespec="seconds"),
        command=command or " ".join(shlex.quote(arg) for arg in sys.argv),
        worker_command=worker_command or "uv run hive start --no-idle-check",
        operator=operator,
        audio_url=audio_url,
        screenshot_paths=list(screenshot_paths or []),
        notes=list(notes or []),
        local=local,
        smoke_result=smoke_result,
        job_status=job_status,
        job_result=job_result,
        job_events=job_events,
        worker_reputation=worker_reputation,
        worker_payouts=worker_payouts,
        billing_before=billing_before,
        billing_after=billing_after,
        follow_up_hints=_dedupe_preserve_order(hints),
    )


def render_real_worker_validation_report(result: RealWorkerValidationResult) -> str:
    lines = [
        "# Real Worker Validation Report",
        "",
        f"**Created:** {result.executed_at}",
        f"**Last Updated:** {result.executed_at}",
        "",
        "## Session",
        "",
        f"- Date: {result.executed_at}",
        f"- Operator: {result.operator or 'unspecified'}",
        f"- Coordinator URL: {result.local.coordinator_url}",
        f"- Worker ID: {result.local.worker_id}",
        f"- Machine model: {result.local.machine_chip or 'unknown'}",
        f"- Memory GB: {result.local.memory_gb if result.local.memory_gb is not None else 'unknown'}",
        f"- Platform: {result.local.platform_summary}",
        (
            "- Whisper backend: "
            f"{result.local.handler_backend} / {result.local.handler_model} / {result.local.handler_quantization}"
        ),
        f"- Token storage: {result.local.token_storage}",
        f"- Audio URL: {result.audio_url}",
        "",
        "## Outcome",
        "",
        f"- Overall result: `{result.overall_result}`",
        (
            "- Blocking issue summary: none"
            if not result.blocking_issues
            else f"- Blocking issue summary: {' | '.join(result.blocking_issues)}"
        ),
        f"- Job ID: {result.smoke_result.job_id or 'not created'}",
        (
            f"- Final status: {result.smoke_result.final_status}"
            if result.smoke_result.final_status
            else "- Final status: unavailable"
        ),
        f"- Elapsed seconds: {result.smoke_result.elapsed_seconds}",
        "",
        "## Commands",
        "",
        "```bash",
        result.command,
        "```",
        "",
        "```bash",
        result.worker_command,
        "```",
        "",
        "## Local Snapshot",
        "",
        "### Status",
        "",
        "```text",
        *result.local.status_lines,
        "```",
        "",
        "### Doctor",
        "",
        "```text",
        *[_render_doctor_check(check) for check in result.local.doctor_checks],
        "```",
        "",
        "## Job Execution",
        "",
        f"- Detail: {result.smoke_result.detail}",
        (
            f"- History: {' -> '.join(result.smoke_result.status_history)}"
            if result.smoke_result.status_history
            else "- History: unavailable"
        ),
        (
            f"- Transcript preview: {result.smoke_result.transcript}"
            if result.smoke_result.transcript
            else "- Transcript preview: unavailable"
        ),
        "",
        "## API Evidence",
        "",
    ]

    lines.extend(_render_probe_section("Job status", result.job_status))
    lines.extend(_render_probe_section("Job result", result.job_result))
    lines.extend(_render_probe_section("Job events", result.job_events))
    lines.extend(_render_probe_section("Worker reputation", result.worker_reputation))
    lines.extend(_render_probe_section("Worker payouts", result.worker_payouts))
    lines.extend(_render_probe_section("Billing balance before job", result.billing_before))
    lines.extend(_render_probe_section("Billing balance after job", result.billing_after))

    lines.extend(
        [
            "## Manual Evidence Still Required",
            "",
            "- Attach a screenshot of the manager dashboard showing the completed job.",
            "- Attach any relevant worker stdout/stderr logs if the run was not clean.",
        ]
    )
    if result.screenshot_paths:
        lines.append(f"- Referenced screenshot paths: {', '.join(result.screenshot_paths)}")
    else:
        lines.append("- Referenced screenshot paths: none yet")

    lines.extend(
        [
            "",
            "## Notes",
            "",
        ]
    )
    if result.notes:
        lines.extend([f"- {note}" for note in result.notes])
    else:
        lines.append("- No operator notes were captured by the script.")

    if result.follow_up_hints:
        lines.extend(
            [
                "",
                "## Follow-up Hints",
                "",
            ]
        )
        lines.extend([f"- {hint}" for hint in result.follow_up_hints])

    return "\n".join(lines) + "\n"


def _resolve_worker_token_probe(raw_config: dict[str, Any], worker_id: str) -> ApiProbe:
    try:
        return ApiProbe(label="worker token", ok=True, payload=resolve_worker_token(raw_config))
    except Exception as exc:
        return ApiProbe(
            label="worker token",
            ok=False,
            error=f"worker token unavailable for {worker_id}: {exc}",
        )


def _fetch_json_probe(
    client: httpx.Client,
    path: str,
    *,
    headers: dict[str, str],
    label: str,
    success_codes: set[int] | None = None,
) -> ApiProbe:
    accepted = success_codes or {200}
    try:
        resp = client.get(path, headers=headers)
    except Exception as exc:
        return ApiProbe(label=label, ok=False, error=f"request failed: {exc}")

    payload: Any
    try:
        payload = resp.json()
    except Exception:
        payload = resp.text.strip() or None

    if resp.status_code in accepted:
        return ApiProbe(label=label, ok=True, status_code=resp.status_code, payload=payload)

    if isinstance(payload, dict) and payload.get("error"):
        error = str(payload["error"])
    elif isinstance(payload, str) and payload:
        error = payload
    else:
        error = f"HTTP {resp.status_code}"
    return ApiProbe(
        label=label,
        ok=False,
        status_code=resp.status_code,
        payload=payload,
        error=error,
    )


def _fetch_billing_balance(client: httpx.Client, headers: dict[str, str]) -> ApiProbe:
    probe = _fetch_json_probe(
        client,
        "/api/v1/billing/balance",
        headers=headers,
        label="billing balance",
    )
    if probe.ok or probe.status_code != 403:
        return probe
    return ApiProbe(
        label=probe.label,
        ok=False,
        status_code=probe.status_code,
        payload=probe.payload,
        error="billing balance unavailable for this API key",
    )


def _render_doctor_check(check: DoctorCheck) -> str:
    label = {"ok": "OK", "warn": "WARN", "fail": "FAIL"}[check.status]
    line = f"[{label}] {check.name:<13} {check.summary}"
    if check.detail:
        return f"{line} | {check.detail}"
    return line


def _render_probe_section(title: str, probe: ApiProbe | None) -> list[str]:
    lines = [f"### {title}", ""]
    if probe is None:
        lines.append("- Not captured")
        lines.append("")
        return lines

    if probe.ok:
        lines.append(f"- Status: HTTP {probe.status_code}")
    else:
        status = f"HTTP {probe.status_code}" if probe.status_code is not None else "request failed"
        lines.append(f"- Status: {status}")
        lines.append(f"- Error: {probe.summary()}")

    if probe.payload is not None:
        lines.append("")
        lines.append("```json")
        lines.append(_render_payload(probe.payload))
        lines.append("```")
    lines.append("")
    return lines


def _render_payload(payload: Any) -> str:
    if isinstance(payload, str):
        return json.dumps(payload, indent=2)
    return json.dumps(payload, indent=2, sort_keys=True, default=str)


def _platform_summary() -> str:
    mac_version = platform.mac_ver()[0]
    if mac_version:
        return f"macOS {mac_version} ({platform.machine()})"
    return f"{platform.system()} {platform.release()} ({platform.machine()})"


def _dedupe_preserve_order(items: list[str]) -> list[str]:
    seen: set[str] = set()
    output: list[str] = []
    for item in items:
        if item in seen:
            continue
        seen.add(item)
        output.append(item)
    return output
