"""Volledige codebase sync naar Gitea main — SysOps."""
from __future__ import annotations

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

from app.db import execute, fetch_all, fetch_one
from app.middleware import log_agent_event

REPO_ROOT = Path(os.getenv("BACKUP_ROOT", "/data/backup-root"))
GITEA_REMOTE = os.getenv(
    "GITEA_BACKUP_REMOTE",
    "http://sysops:Foodlinkk%23SysOps2026@gitea:3001/aissa/foodlinkk-command-center.git",
)
GITEA_GIT_USER = os.getenv("GITEA_SYSOPS_USER", "sysops")
GITEA_GIT_EMAIL = os.getenv("GITEA_SYSOPS_EMAIL", "sysops@foodlinkk.local")
GITEA_GIT_NAME = os.getenv("GITEA_SYSOPS_NAME", "sysops")
GITEA_BRANCH = os.getenv("GITEA_BACKUP_BRANCH", "main")

SKIP_NAMES = {
    ".git",
    "__pycache__",
    ".env",
    "node_modules",
    ".cursor",
    ".venv",
    "venv",
    "*.pyc",
}


def _iso_now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _run(cmd: list[str], cwd: Path | None = None, timeout: int = 180) -> dict[str, Any]:
    try:
        proc = subprocess.run(  # noqa: S603
            cmd,
            cwd=str(cwd) if cwd else None,
            capture_output=True,
            text=True,
            timeout=timeout,
            check=False,
        )
    except subprocess.TimeoutExpired:
        return {"ok": False, "error": "timeout", "cmd": cmd}
    except FileNotFoundError as exc:
        return {"ok": False, "error": str(exc), "cmd": cmd}
    return {
        "ok": proc.returncode == 0,
        "code": proc.returncode,
        "stdout": (proc.stdout or "").strip(),
        "stderr": (proc.stderr or "").strip(),
        "cmd": cmd,
    }


def _table_exists(name: str) -> bool:
    row = fetch_one(
        """
        SELECT EXISTS(
            SELECT 1 FROM information_schema.tables
            WHERE table_schema = 'public' AND table_name = %s
        ) AS ok
        """,
        (name,),
    )
    return bool(row and row.get("ok"))


def _record_activity(
    action_type: str,
    title: str,
    body: str = "",
    commit_ref: str | None = None,
    files_changed: int = 0,
    status: str = "success",
    metadata: dict | None = None,
) -> dict[str, Any] | None:
    if not _table_exists("sysops_activity"):
        return None
    row = fetch_one(
        """
        INSERT INTO sysops_activity (action_type, title, body, commit_ref, files_changed, status, metadata)
        VALUES (%s, %s, %s, %s, %s, %s, %s::jsonb)
        RETURNING id, created_at
        """,
        (action_type, title, body, commit_ref, files_changed, status, json.dumps(metadata or {})),
    )
    return dict(row) if row else None


def list_activity(limit: int = 30) -> list[dict[str, Any]]:
    if not _table_exists("sysops_activity"):
        return []
    rows = fetch_all(
        "SELECT * FROM sysops_activity ORDER BY created_at DESC LIMIT %s",
        (max(1, min(limit, 100)),),
    )
    out = []
    for r in rows:
        item = dict(r)
        if hasattr(item.get("created_at"), "isoformat"):
            item["created_at"] = item["created_at"].isoformat()
        out.append(item)
    return out


def _should_skip(name: str) -> bool:
    if name in SKIP_NAMES:
        return True
    if name.endswith(".pyc") or name.endswith(".pyo"):
        return True
    return False


def _copy_tree(src: Path, dest: Path) -> int:
    """Kopieer repo-inhoud naar git worktree; retour aantal bestanden."""
    count = 0
    if not src.exists():
        return 0
    for item in src.iterdir():
        if _should_skip(item.name):
            continue
        target = dest / item.name
        if item.is_file():
            target.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(item, target)
            count += 1
        elif item.is_dir():
            if target.exists():
                shutil.rmtree(target, ignore_errors=True)
            shutil.copytree(
                item,
                target,
                ignore=shutil.ignore_patterns("__pycache__", "*.pyc", ".env", "node_modules", ".git"),
            )
            count += sum(1 for _ in target.rglob("*") if _.is_file())
    return count


def run_gitea_repo_sync(reason: str = "deploy", approval_request_id: int | None = None) -> dict[str, Any]:
    """Push volledige codebase naar Gitea main branch."""
    work = Path("/tmp/foodlinkk-gitea-sync")
    if work.exists():
        shutil.rmtree(work, ignore_errors=True)
    work.mkdir(parents=True, exist_ok=True)

    if not REPO_ROOT.exists():
        msg = f"Repo root niet gevonden: {REPO_ROOT}"
        _record_activity("gitea_sync", "Gitea sync mislukt", msg, status="failed")
        return {"ok": False, "message": msg}

    repo = work / "repo"
    clone = _run(["git", "clone", "--depth", "1", "-b", GITEA_BRANCH, GITEA_REMOTE, str(repo)])
    if not clone.get("ok"):
        repo = work
        _run(["git", "init", "-b", GITEA_BRANCH], cwd=repo)
        _run(["git", "remote", "add", "origin", GITEA_REMOTE], cwd=repo)

    _run(["git", "config", "user.email", GITEA_GIT_EMAIL], cwd=repo)
    _run(["git", "config", "user.name", GITEA_GIT_NAME], cwd=repo)

    copied = _copy_tree(REPO_ROOT, repo)
    stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    commit_msg = f"SysOps: {reason} — {stamp}"

    _run(["git", "add", "-A"], cwd=repo)
    commit = _run(["git", "commit", "-m", commit_msg], cwd=repo)
    nothing = "nothing to commit" in (commit.get("stdout", "") + commit.get("stderr", ""))
    if not commit.get("ok") and not nothing:
        msg = "Git commit mislukt: " + (commit.get("stderr") or "")
        _record_activity("gitea_sync", "Gitea commit mislukt", msg, files_changed=copied, status="failed")
        return {"ok": False, "message": msg, "copied": copied}

    commit_ref = None
    if not nothing:
        push = _run(["git", "push", "-u", "origin", GITEA_BRANCH], cwd=repo, timeout=300)
        if not push.get("ok"):
            msg = "Git push mislukt: " + (push.get("stderr") or "")
            _record_activity("gitea_sync", "Gitea push mislukt", msg, files_changed=copied, status="failed")
            return {"ok": False, "message": msg, "copied": copied}
        rev = _run(["git", "rev-parse", "--short", "HEAD"], cwd=repo)
        commit_ref = rev.get("stdout") or None

    summary = (
        f"Volledige codebase gesynchroniseerd naar Gitea ({reason}). "
        f"{copied} bestanden · commit {commit_ref or 'geen wijzigingen'}"
    )
    activity = _record_activity(
        "gitea_sync",
        f"Gitea push: {reason}",
        summary,
        commit_ref=commit_ref,
        files_changed=copied,
        status="success" if (commit_ref or nothing) else "skipped",
        metadata={"reason": reason, "approval_request_id": approval_request_id, "nothing_to_commit": nothing},
    )

    try:
        log_agent_event(
            agent_name="sysops",
            agent_type="sysops",
            event_type="gitea_sync",
            title=f"Gitea sync: {reason}" + (f" ({commit_ref})" if commit_ref else " — geen wijzigingen"),
            body=summary,
            metadata={
                "commit_ref": commit_ref,
                "files_changed": copied,
                "reason": reason,
                "for_herman": True,
            },
            channel="sysops",
        )
    except Exception:
        pass

    if _table_exists("config_backups"):
        fetch_one(
            """
            INSERT INTO config_backups (approval_request_id, commit_ref, files_count, status, message, payload)
            VALUES (%s, %s, %s, %s, %s, %s::jsonb)
            RETURNING id
            """,
            (
                approval_request_id,
                commit_ref,
                copied,
                "success" if commit_ref else "unchanged",
                summary,
                json.dumps({"reason": reason, "full_repo": True}),
            ),
        )

    return {
        "ok": True,
        "message": summary,
        "commit_ref": commit_ref,
        "files_count": copied,
        "nothing_to_commit": nothing,
        "activity_id": (activity or {}).get("id"),
    }
