"""Upload attestations to Gjalla cloud."""

import json
import subprocess
from pathlib import Path

import click
import httpx
import yaml

from ..config.settings import Settings
from ..display.output import console, info, success, warning, error
from ..protocol.http_client import fetch_context
from ._cache import write_cache_files
from ._api import _make_api_headers
from ._shared import get_repo_root, load_local_config, local_config_exists, strip_yaml_delimiters


def _upload_attestations(settings: Settings, repo_root: Path, attestations_dir: Path) -> None:
    """Upload attestations to gjalla cloud."""
    if not local_config_exists(repo_root):
        # Check old format (.gjalla as a file)
        old_config = repo_root / ".gjalla"
        if not old_config.is_file():
            info("No .gjalla config — skipping attestation upload")
            return

    config = load_local_config(repo_root)
    if not config:
        warning("Invalid .gjalla config — skipping attestation upload")
        return

    project_id = config.get("project_id")
    api_url = config.get("api_url", settings.api_url)

    if not project_id:
        warning("No project_id in .gjalla config — attestations logged locally only")
        return

    # Read attestation file if present and append to log
    attestation_path = repo_root / ".gjalla" / ".commit-attestation.md"
    gjallalog_path = repo_root / ".gjalla" / "log.jsonl"

    # Migrate legacy .gjallalog to .gjalla/log.jsonl
    legacy_log = repo_root / ".gjallalog"
    if legacy_log.exists():
        with open(gjallalog_path, "a", encoding="utf-8") as f:
            f.write(legacy_log.read_text(encoding="utf-8"))
        legacy_log.unlink()
        info("Migrated .gjallalog → .gjalla/log.jsonl")

    if attestation_path.exists():
        _log_attestation(attestation_path, gjallalog_path, attestations_dir)
        attestation_path.unlink()
        info("Logged attestation and cleaned up .gjalla/.commit-attestation.md")

    # Upload unsent log entries
    if not gjallalog_path.exists():
        info("No attestation log entries to upload")
        return

    api_key = settings.api_key
    if not api_key:
        warning("No API key configured — attestations logged locally only")
        return

    lines = gjallalog_path.read_text(encoding="utf-8").strip().splitlines()
    unsent = []
    for line in lines:
        try:
            entry = json.loads(line)
            # Backward compat: handle both "uploaded" and "synced" keys
            if not entry.get("uploaded") and not entry.get("synced"):
                # For new-format entries (no embedded attestation), load from file
                if "attestation" not in entry and "raw_attestation" not in entry:
                    commit = entry.get("commit", "")
                    att_file = attestations_dir / f"{commit}.yaml"
                    if att_file.exists():
                        docs = list(yaml.safe_load_all(att_file.read_text(encoding="utf-8")))
                        entry["attestation"] = docs[0] if docs else {}
                unsent.append(entry)
        except json.JSONDecodeError:
            pass

    if not unsent:
        info("All attestations already uploaded")
        return

    info(f"Uploading {len(unsent)} attestation(s)...")

    try:
        response = httpx.post(
            f"{api_url}/api/agent/projects/{project_id}/attestations",
            headers=_make_api_headers(api_key),
            json={"attestations": unsent},
            timeout=30.0,
        )

        if response.status_code == 200:
            data = response.json()
            inserted = data.get("inserted", 0)
            success(f"Uploaded {inserted} attestation(s)")

            # Mark all as uploaded
            updated_lines = []
            for line in lines:
                try:
                    entry = json.loads(line)
                    entry["uploaded"] = True
                    entry["synced"] = True
                    updated_lines.append(json.dumps(entry))
                except json.JSONDecodeError:
                    updated_lines.append(line)
            gjallalog_path.write_text("\n".join(updated_lines) + "\n", encoding="utf-8")
        else:
            warning(f"Upload failed (HTTP {response.status_code}). Entries saved locally.")
    except httpx.RequestError as e:
        warning(f"Upload failed: {e}. Entries saved locally.")


def _parse_attestation_yaml(content: str) -> dict:
    """Parse attestation YAML frontmatter, stripping --- delimiters."""
    try:
        return yaml.safe_load(strip_yaml_delimiters(content)) or {}
    except yaml.YAMLError:
        return {}


def _log_attestation(attestation_path: Path, gjallalog_path: Path, attestations_dir: Path) -> None:
    """Parse attestation YAML, save full body to file, append minimal entry to log."""
    content = attestation_path.read_text(encoding="utf-8")
    parsed = _parse_attestation_yaml(content)

    # Get commit hash and branch
    try:
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            capture_output=True, text=True,
        )
        commit = result.stdout.strip() if result.returncode == 0 else "unknown"
    except OSError:
        commit = "unknown"

    try:
        result = subprocess.run(
            ["git", "rev-parse", "--abbrev-ref", "HEAD"],
            capture_output=True, text=True,
        )
        branch = result.stdout.strip() if result.returncode == 0 else "unknown"
    except OSError:
        branch = "unknown"

    # Save full attestation to individual YAML file
    attestations_dir.mkdir(parents=True, exist_ok=True)
    att_file = attestations_dir / f"{commit}.yaml"
    att_file.write_text(yaml.dump(parsed, default_flow_style=False, sort_keys=False), encoding="utf-8")

    # Append minimal entry to .gjalla/log.jsonl
    entry = {
        "commit": commit,
        "branch": branch,
        "timestamp": parsed.get("timestamp", ""),
        "agent": parsed.get("agent", ""),
        "summary": parsed.get("summary", ""),
        "synced": False,
    }
    if parsed.get("agent_id"):
        entry["agent_id"] = parsed["agent_id"]

    with open(gjallalog_path, "a") as f:
        f.write(json.dumps(entry, default=str) + "\n")


def _refresh_context_cache(settings: Settings, repo_root: Path) -> None:
    """Fetch project context from API and write local cache files."""
    config = load_local_config(repo_root)
    if not config:
        return

    project_id = config.get("project_id")

    if not project_id:
        return

    api_url = config.get("api_url", settings.api_url)

    console.print()
    info("[bold]Refreshing project context cache...[/bold]")

    try:
        context = fetch_context(project_id, settings.api_key, api_url)
        write_cache_files(repo_root, context)
        success("Project context cached to .gjalla/cache/")
    except RuntimeError as e:
        warning(f"Could not refresh context cache: {e}")


@click.command()
def sync() -> None:
    """Upload pending attestations to gjalla cloud.

    Reads the local .gjalla/log.jsonl file and uploads any entries
    that haven't been sent yet.

    \b
    Examples:
        gjalla sync    # Upload pending attestations
    """
    repo_root = get_repo_root()

    console.print("[bold]gjalla Sync[/bold]")
    console.print()

    # Check for local-only mode
    config = load_local_config(repo_root)
    local_arch_path = config.get("local_arch_path")
    local_rules_path = config.get("local_rules_path")
    # Backward compat: fall back to legacy single-path key
    if not local_arch_path and not local_rules_path:
        legacy = config.get("local_docs_path")
        if legacy:
            local_arch_path = legacy
            local_rules_path = legacy
    project_id = config.get("project_id")

    if (local_arch_path or local_rules_path) and not project_id:
        from pathlib import Path as _Path
        for label, p in [("Architecture", local_arch_path), ("Rules", local_rules_path)]:
            if not p:
                continue
            if _Path(p).exists():
                success(f"Local-only mode — {label.lower()} path valid: {p}")
            else:
                warning(f"Local {label.lower()} path no longer exists: {p}")
                info("Run [bold]gjalla setup[/bold] to update the path.")
        info("No remote project configured. Run [bold]gjalla connect[/bold] to link one.")
        return

    settings = Settings.load()

    # Prefer local project config for API key and URL over global config
    from ._shared import resolve_local_api_key
    local_key, local_url = resolve_local_api_key(repo_root)
    updates = {}
    if local_key:
        updates["api_key"] = local_key
    if local_url:
        updates["api_url"] = local_url
    if updates:
        settings = settings.model_copy(update=updates)

    if not settings.api_key:
        error("API key not configured. Run 'gjalla init' or 'gjalla connect' first.")
        raise SystemExit(1)

    attestations_dir = repo_root / ".gjalla" / "attestations"
    info("[bold]Uploading attestations...[/bold]")
    _upload_attestations(settings, repo_root, attestations_dir)

    # Refresh project context cache
    _refresh_context_cache(settings, repo_root)
