"""
Background daemon: polling loop and sync cycle.
Every N seconds: git detection + intent read → POST context → GET state → write context.md.
PRD §6.2/§6.3: terminal notifications for overlap warnings and push.
Phase 4: PID file for daemon lifecycle; stop kills the background process.
"""

import json
import os
import signal
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from .api import get_context, post_context
from .config import load_config
from .context import build_payload, render_context_md, write_context_file
from .git import detect_changes
from .intent import read_intent

LAST_SYNC_FILENAME = Path(".dryft") / "last_sync.json"
DAEMON_PID_FILENAME = Path(".dryft") / "daemon.pid"


def _print_banner() -> None:
    o1 = "\x1b[38;5;202m"  # dark orange
    o2 = "\x1b[38;5;208m"  # orange
    o3 = "\x1b[38;5;214m"  # light orange
    y1 = "\x1b[38;5;220m"  # golden yellow
    y2 = "\x1b[38;5;226m"  # bright yellow
    y3 = "\x1b[38;5;228m"  # light yellow
    r = "\x1b[0m"           # reset
    print(f"""
{o1} ██████╗ ██████╗ ██╗   ██╗███████╗████████╗{r}
{o2} ██╔══██╗██╔══██╗╚██╗ ██╔╝██╔════╝╚══██╔══╝{r}
{o3} ██║  ██║██████╔╝ ╚████╔╝ █████╗     ██║{r}
{y1} ██║  ██║██╔══██╗  ╚██╔╝  ██╔══╝     ██║{r}
{y2} ██████╔╝██║  ██║   ██║   ██║        ██║{r}
{y3} ╚═════╝ ╚═╝  ╚═╝   ╚═╝   ╚═╝        ╚═╝{r}
""")


def _print_goodbye_banner() -> None:
    y3 = "\x1b[38;5;228m"  # light yellow
    y2 = "\x1b[38;5;226m"  # bright yellow
    y1 = "\x1b[38;5;220m"  # golden yellow
    o3 = "\x1b[38;5;214m"  # light orange
    o2 = "\x1b[38;5;208m"  # orange
    o1 = "\x1b[38;5;202m"  # dark orange
    r = "\x1b[0m"           # reset
    print(f"""
{y3}  ██████╗  ██████╗  ██████╗ ██████╗ ██████╗ ██╗   ██╗███████╗{r}
{y2} ██╔════╝ ██╔═══██╗██╔═══██╗██╔══██╗██╔══██╗╚██╗ ██╔╝██╔════╝{r}
{y1} ██║  ███╗██║   ██║██║   ██║██║  ██║██████╔╝ ╚████╔╝ █████╗{r}
{o3} ██║   ██║██║   ██║██║   ██║██║  ██║██╔══██╗  ╚██╔╝  ██╔══╝{r}
{o2} ╚██████╔╝╚██████╔╝╚██████╔╝██████╔╝██████╔╝   ██║   ███████╗{r}
{o1}  ╚═════╝  ╚═════╝  ╚═════╝ ╚═════╝ ╚═════╝    ╚═╝   ╚══════╝{r}

{y3}     ███████╗ ██████╗ ██████╗     ███╗   ██╗ ██████╗ ██╗    ██╗{r}
{y2}     ██╔════╝██╔═══██╗██╔══██╗    ████╗  ██║██╔═══██╗██║    ██║{r}
{y1}     █████╗  ██║   ██║██████╔╝    ██╔██╗ ██║██║   ██║██║ █╗ ██║{r}
{o3}     ██╔══╝  ██║   ██║██╔══██╗    ██║╚██╗██║██║   ██║██║███╗██║{r}
{o2}     ██║     ╚██████╔╝██║  ██║    ██║ ╚████║╚██████╔╝╚███╔███╔╝{r}
{o1}     ╚═╝      ╚═════╝ ╚═╝  ╚═╝    ╚═╝  ╚═══╝ ╚═════╝  ╚══╝╚══╝{r}
""")


def _read_last_sync(cwd: str) -> dict:
    path = Path(cwd) / LAST_SYNC_FILENAME
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
        return data if isinstance(data, dict) else {}
    except (OSError, json.JSONDecodeError):
        return {}


def _run_sync(cwd: str) -> None:
    config = load_config(cwd)
    if not config:
        return
    previous = _read_last_sync(cwd)
    git_changes = detect_changes(cwd)
    intent = read_intent(cwd)
    payload = build_payload(
        config["developer_id"],
        git_changes,
        intent,
        config.get("project_id"),
    )
    post_context(config["server_url"], payload)
    state = get_context(config["server_url"], config["project_id"])
    markdown = render_context_md(state, config["developer_id"])
    write_context_file(cwd, markdown)

    # PRD §6.3: terminal notification when overlap detected (deduplicated)
    warnings = state.get("warnings") or []
    previous_warnings = previous.get("last_warnings") or []

    # Create unique keys for warnings to detect changes
    def warning_key(w: dict) -> str:
        return f"{w.get('file', '?')}:{w.get('detail', '')}:{w.get('type', '')}"

    previous_keys = {warning_key(w) for w in previous_warnings}
    current_keys = {warning_key(w) for w in warnings}

    # Show new warnings
    new_warnings = [w for w in warnings if warning_key(w) not in previous_keys]
    for w in new_warnings:
        f = w.get("file", "?")
        detail = w.get("detail", "")
        print(f"⚠ Overlap detected on {f}: {detail}", file=sys.stderr)

    # Show resolved warnings
    resolved_warnings = [w for w in previous_warnings if warning_key(w) not in current_keys]
    for w in resolved_warnings:
        f = w.get("file", "?")
        print(f"✓ Overlap resolved on {f}", file=sys.stderr)

    # Show summary if warnings exist (only on changes or first time)
    if warnings and (new_warnings or resolved_warnings):
        print(f"📊 Active overlaps: {len(warnings)}", file=sys.stderr)

    # PRD §6.2 Step 9 / §10.3: terminal notification when other dev pushed
    last_push: dict[str, Any] = state.get("last_push") or {}
    if last_push.get("at") and last_push.get("developer") != config["developer_id"]:
        seen = previous.get("last_push_seen") or {}
        is_new = seen.get("at") != last_push.get("at") or seen.get("developer") != last_push.get("developer")
        if is_new:
            print(f"{last_push.get('developer')} pushed changes. Consider pulling before continuing.", file=sys.stderr)

    next_data: dict[str, Any] = {
        "last_sync": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
        "error": None,
        "last_warnings": warnings,
    }
    if last_push.get("at"):
        next_data["last_push_seen"] = {"at": last_push["at"], "developer": last_push.get("developer")}
    else:
        next_data["last_push_seen"] = previous.get("last_push_seen")

    last_sync_path = Path(cwd) / LAST_SYNC_FILENAME
    last_sync_path.write_text(json.dumps(next_data, indent=2), encoding="utf-8")


def _run_sync_and_log(cwd: str) -> None:
    last_sync_path = Path(cwd) / LAST_SYNC_FILENAME
    try:
        _run_sync(cwd)
    except Exception as e:
        msg = str(e)
        try:
            last_sync_path.write_text(
                json.dumps(
                    {"last_sync": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"), "error": msg},
                    indent=2,
                ),
                encoding="utf-8",
            )
        except OSError:
            pass
        print("Dryft sync error:", msg, file=sys.stderr)


def _pid_path(cwd: str) -> Path:
    return Path(cwd) / DAEMON_PID_FILENAME


def _remove_pid_file(cwd: str) -> None:
    try:
        _pid_path(cwd).unlink()
    except OSError:
        pass


def run_daemon_loop(
    *,
    cwd: str | None = None,
    poll_interval_ms: int | None = None,
) -> None:
    """Run the sync loop in the current process (used by --foreground)."""
    import time

    _print_banner()
    cwd = cwd or str(Path.cwd())
    if isinstance(cwd, Path):
        cwd = str(cwd)
    config = load_config(cwd)
    if not config:
        raise SystemExit("Run dryft init first.")
    interval_ms = poll_interval_ms or config.get("poll_interval_ms") or 5_000
    interval_sec = interval_ms / 1000.0

    def shutdown(*_args: object) -> None:
        _remove_pid_file(cwd)
        sys.exit(0)

    signal.signal(signal.SIGINT, shutdown)
    signal.signal(signal.SIGTERM, shutdown)

    _run_sync_and_log(cwd)
    print(f"Dryft daemon running. Syncing every {interval_sec:.0f} s. Ctrl+C to stop.")
    try:
        while True:
            time.sleep(interval_sec)
            _run_sync_and_log(cwd)
    except KeyboardInterrupt:
        shutdown()


def start_daemon(
    *,
    cwd: str | None = None,
    poll_interval_ms: int | None = None,
    foreground: bool = False,
) -> None:
    cwd = cwd or str(Path.cwd())
    if isinstance(cwd, Path):
        cwd = str(cwd)

    if foreground:
        run_daemon_loop(cwd=cwd, poll_interval_ms=poll_interval_ms)
        return

    _print_banner()

    config = load_config(cwd)
    if not config:
        raise SystemExit("Run dryft init first.")

    pid_path = _pid_path(cwd)
    if pid_path.exists():
        try:
            existing_pid = int(pid_path.read_text().strip())
            os.kill(existing_pid, 0)
            print("Daemon already running (PID", existing_pid, "). Use 'dryft stop' first.", file=sys.stderr)
            sys.exit(1)
        except (ValueError, ProcessLookupError, OSError):
            pid_path.unlink(missing_ok=True)

    proc = subprocess.Popen(
        [sys.executable, "-m", "dryft.cli", "start", "--foreground"],
        cwd=cwd,
        start_new_session=True,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    if proc.pid is not None:
        pid_path.parent.mkdir(parents=True, exist_ok=True)
        pid_path.write_text(str(proc.pid), encoding="utf-8")
        print("Dryft daemon started in background (PID", proc.pid, "). Use 'dryft stop' to stop.")
    else:
        print("Failed to start daemon.", file=sys.stderr)
        sys.exit(1)


def stop_daemon(cwd: str | None = None) -> None:
    cwd = cwd or str(Path.cwd())
    if isinstance(cwd, Path):
        cwd = str(cwd)
    pid_path = _pid_path(cwd)
    try:
        pid_str = pid_path.read_text(encoding="utf-8").strip()
    except OSError:
        print("Daemon not running (no PID file).")
        return
    try:
        pid = int(pid_str)
    except ValueError:
        print("Daemon not running (invalid PID file).")
        pid_path.unlink(missing_ok=True)
        return
    try:
        os.kill(pid, signal.SIGTERM)
        pid_path.unlink(missing_ok=True)
        _print_goodbye_banner()
        print("Dryft daemon stopped (PID", pid, ").")
    except ProcessLookupError:
        pid_path.unlink(missing_ok=True)
        _print_goodbye_banner()
        print("Daemon was not running (stale PID file removed).")
    except OSError as e:
        print("Could not stop daemon:", e, file=sys.stderr)
        sys.exit(1)
