"""License management commands for Ed25519-signed entitlement files."""

from __future__ import annotations

import json
import os
import tempfile
from pathlib import Path
from typing import Any
from urllib.parse import urlsplit

import click
import httpx

from g_gremlin.credentials import license as license_mod
from g_gremlin.credentials import metering
from g_gremlin.credentials.entitlements import get_cli_entitlements_config
from g_gremlin.credentials.license_v2 import LICENSE_PATH, verify_license

EXIT_SUCCESS = 0
EXIT_ISSUES = 1
EXIT_USAGE = 2


def _normalize_base_url(value: str | None) -> str | None:
    if not value:
        return None
    normalized = value.strip().rstrip("/")
    return normalized or None


def _looks_like_html(response: Any) -> bool:
    headers = getattr(response, "headers", {}) or {}
    content_type = str(headers.get("content-type", "")).lower()
    if "text/html" in content_type:
        return True
    text = str(getattr(response, "text", "") or "").lstrip().lower()
    return text.startswith("<!doctype html") or text.startswith("<html")


def _fallback_api_base(base: str) -> str | None:
    parsed = urlsplit(base)
    if parsed.hostname != "app.foundryops.io":
        return None
    scheme = parsed.scheme or "https"
    port = f":{parsed.port}" if parsed.port else ""
    return f"{scheme}://api.foundryops.io{port}"


def _resolve_license_api_bases() -> list[str]:
    configured: list[str] = []
    for raw in (os.getenv("FOUNDRYOPS_API_URL"), os.getenv("FOUNDRYMATCH_API_URL")):
        base = _normalize_base_url(raw)
        if base and base not in configured:
            configured.append(base)

    if configured:
        fallback = _fallback_api_base(configured[0])
        if fallback and fallback not in configured:
            configured.append(fallback)
        return configured

    return ["https://api.foundryops.io", "https://app.foundryops.io"]


def _load_license_payload_from_token(token: str) -> dict[str, Any]:
    candidates = _resolve_license_api_bases()
    attempts: list[str] = []

    for index, api_base in enumerate(candidates):
        endpoint = f"{api_base}/api/v1/agent/licenses/resolve"
        try:
            response = httpx.post(
                endpoint,
                json={"token": token},
                timeout=30.0,
            )
        except httpx.HTTPError as exc:
            attempts.append(f"{api_base} ({exc})")
            continue

        if response.status_code == 200:
            try:
                payload = response.json()
            except Exception as exc:
                if index < (len(candidates) - 1) and _looks_like_html(response):
                    attempts.append(f"{api_base} (unexpected HTML response)")
                    continue
                raise click.ClickException(
                    f"License resolve endpoint returned invalid JSON: {exc}"
                ) from exc

            if not isinstance(payload, dict):
                raise click.ClickException("License resolve endpoint returned invalid payload.")
            return payload

        if (
            index < (len(candidates) - 1)
            and response.status_code == 404
            and _looks_like_html(response)
        ):
            attempts.append(f"{api_base} (HTML 404)")
            continue

        raise click.ClickException(
            f"Failed to resolve install token ({response.status_code}): {response.text[:200]}"
        )

    attempted = ", ".join(candidates)
    attempt_details = f" ({'; '.join(attempts)})" if attempts else ""
    raise click.ClickException(
        "Failed to resolve install token: no compatible license resolve endpoint responded. "
        f"Tried: {attempted}{attempt_details}"
    )


def _install_license_file(source_path: Path) -> dict[str, Any]:
    check = verify_license(source_path)
    if not check.valid or not check.payload:
        raise click.ClickException(check.error or "License file verification failed.")

    LICENSE_PATH.parent.mkdir(parents=True, exist_ok=True)

    previous_license_id: str | None = None
    if LICENSE_PATH.exists():
        previous = verify_license(LICENSE_PATH)
        if previous.valid and previous.payload:
            previous_license_id = previous.payload.license_id

    LICENSE_PATH.write_text(source_path.read_text(encoding="utf-8"), encoding="utf-8")

    installed = verify_license(LICENSE_PATH)
    if not installed.valid or not installed.payload:
        raise click.ClickException(installed.error or "Installed license failed verification.")

    current_license_id = installed.payload.license_id
    if previous_license_id and previous_license_id != current_license_id:
        metering.reset_meters_for_license(previous_license_id)

    return {
        "license_id": current_license_id,
        "plan": installed.payload.plan,
        "expires_at": installed.payload.expires_at,
    }


def _install_license_from_token(token: str) -> dict[str, Any]:
    payload = _load_license_payload_from_token(token)
    with tempfile.NamedTemporaryFile("w", encoding="utf-8", suffix=".json", delete=False) as handle:
        temp_path = Path(handle.name)
        handle.write(json.dumps(payload, indent=2))
    try:
        return _install_license_file(temp_path)
    finally:
        temp_path.unlink(missing_ok=True)


@click.group("license")
def license_group() -> None:
    """Manage signed CLI license files and entitlement status."""


@license_group.command("status")
@click.option("--json", "json_output", is_flag=True, help="Output machine-readable JSON.")
def license_status(json_output: bool) -> None:
    """Show effective plan, entitlements, limits, and meter usage."""
    result = license_mod.check_license_unified()
    config = get_cli_entitlements_config()
    license_id = str(result.license_id or "local_free")
    summaries = metering.get_all_meter_summaries(result.limits, config.limits, license_id=license_id)

    if json_output:
        click.echo(
            json.dumps(
                {
                    "valid": result.valid,
                    "source": result.source,
                    "plan": result.plan,
                    "trial": result.trial,
                    "license_id": result.license_id,
                    "account_id": result.account_id,
                    "issued_at": result.issued_at,
                    "expires_at": result.expires_at,
                    "grace_period_hours": result.grace_period_hours,
                    "schema_version": result.schema_version,
                    "issuer": result.issuer,
                    "kid": result.kid,
                    "entitlements_hash": result.entitlements_hash,
                    "error": result.error,
                    "warnings": result.warnings,
                    "entitlements": result.entitlements,
                    "limits": result.limits,
                    "meters": [
                        {
                            "meter_key": s.meter_key,
                            "period": s.period,
                            "period_key": s.period_key,
                            "used": s.used,
                            "cap": s.cap,
                            "reset_at": s.reset_at,
                        }
                        for s in summaries
                    ],
                },
                indent=2,
            )
        )
        return

    click.echo("g-gremlin License Status")
    click.echo(f"Plan: {config.tier_labels.get(result.plan or 'local_free', result.plan or 'local_free')}")
    click.echo(f"Source: {result.source}")
    if result.expires_at:
        click.echo(f"Expires: {result.expires_at}")
    if result.license_id:
        click.echo(f"License ID: {result.license_id}")
    if result.entitlements_hash:
        click.echo(f"Entitlements hash: {result.entitlements_hash}")
    if result.warnings:
        click.echo("Warnings:")
        for warning in result.warnings:
            click.echo(f"  - {warning}")
    if result.error:
        click.echo(f"Error: {result.error}")

    click.echo("Top meter usage:")
    sorted_meters = sorted(
        summaries,
        key=lambda s: (0.0 if s.cap in (None, 0) else (s.used / float(s.cap))),
        reverse=True,
    )
    for meter in sorted_meters[:5]:
        if meter.cap in (None, 0):
            pct = "n/a"
            cap_text = "unlimited" if meter.cap is None else str(meter.cap)
        else:
            pct = f"{(meter.used / float(meter.cap)) * 100:.1f}%"
            cap_text = str(meter.cap)
        reset_text = meter.reset_at or "n/a"
        click.echo(
            f"  - {meter.meter_key}: {meter.used}/{cap_text} ({pct}), reset {reset_text}"
        )


@license_group.command("install")
@click.argument("path_or_token")
def license_install(path_or_token: str) -> None:
    """Install a signed license from file path or install token."""
    candidate = Path(path_or_token).expanduser()
    try:
        if candidate.exists() and candidate.is_file():
            installed = _install_license_file(candidate)
        else:
            installed = _install_license_from_token(path_or_token)
    except click.ClickException:
        raise
    except Exception as exc:
        raise click.ClickException(f"Failed to install license: {exc}") from exc

    click.echo("License installed.")
    click.echo(f"Plan: {installed['plan']}")
    click.echo(f"License ID: {installed['license_id']}")
    click.echo(f"Expires: {installed['expires_at']}")


@license_group.command("refresh")
@click.option("--token", help="Install token used to refresh license.")
def license_refresh(token: str | None) -> None:
    """Refresh license by resolving a fresh signed payload from token."""
    resolved_token = token or os.getenv("G_GREMLIN_LICENSE_INSTALL_TOKEN")
    if not resolved_token:
        click.echo(
            "Error: Provide --token or set G_GREMLIN_LICENSE_INSTALL_TOKEN to refresh license.",
            err=True,
        )
        raise click.exceptions.Exit(EXIT_USAGE)

    installed = _install_license_from_token(resolved_token)
    click.echo("License refreshed.")
    click.echo(f"Plan: {installed['plan']}")
    click.echo(f"Expires: {installed['expires_at']}")
