"""
Health Check HTTP Server for Claudia Memory System

Provides a simple HTTP endpoint to check daemon status.
"""

import asyncio
import json
import logging
import threading
from datetime import datetime
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from typing import Any, Callable, Dict, Optional

from ..config import get_config
from ..database import get_db
from ..embeddings import get_embedding_service
from .scheduler import get_scheduler

logger = logging.getLogger(__name__)


def build_status_report(*, db=None) -> dict:
    """Build a comprehensive status report for the memory system.

    Returns a dict with schema version, component health, job list, and counts.
    Used by both the HTTP /status endpoint and the MCP system_health tool.

    Args:
        db: Optional database instance. If None, uses the global get_db() singleton.
    """
    config = get_config()
    report = {
        "timestamp": datetime.utcnow().isoformat(),
        "status": "healthy",
        "db_path": str(config.db_path),
        "schema_version": 0,
        "components": {},
        "scheduled_jobs": [],
        "counts": {},
    }

    # Database check + schema version
    try:
        _db = db or get_db()
        _db.execute("SELECT 1", fetch=True)
        report["components"]["database"] = "ok"

        # Schema version
        try:
            rows = _db.execute(
                "SELECT MAX(version) as v FROM schema_migrations", fetch=True
            )
            report["schema_version"] = rows[0]["v"] if rows and rows[0]["v"] else 0
        except Exception:
            report["schema_version"] = 0

        # Unified DB identity
        try:
            meta_rows = _db.execute(
                "SELECT value FROM _meta WHERE key = 'unified_db'", fetch=True
            )
            report["unified_db"] = (
                meta_rows[0]["value"] == "true" if meta_rows else False
            )
        except Exception:
            report["unified_db"] = False

        # Counts
        for table, query in [
            ("memories", "SELECT COUNT(*) as c FROM memories"),
            ("entities", "SELECT COUNT(*) as c FROM entities WHERE deleted_at IS NULL"),
            ("relationships", "SELECT COUNT(*) as c FROM relationships"),
            ("episodes", "SELECT COUNT(*) as c FROM episodes"),
            ("patterns", "SELECT COUNT(*) as c FROM patterns WHERE is_active = 1"),
            ("reflections", "SELECT COUNT(*) as c FROM reflections"),
        ]:
            try:
                rows = _db.execute(query, fetch=True)
                report["counts"][table] = rows[0]["c"] if rows else 0
            except Exception:
                report["counts"][table] = -1

        # Backup status (check both new backups/ dir and legacy alongside-DB location)
        try:
            import glob
            backup_dir = config.backup_dir
            new_pattern = str(backup_dir / "claudia-*.db")
            old_pattern = f"{config.db_path}.backup-*.db"
            backups = sorted(
                glob.glob(new_pattern) + glob.glob(old_pattern),
                key=lambda p: Path(p).stat().st_mtime if Path(p).exists() else 0,
            )
            if backups:
                latest = Path(backups[-1])
                report["backup"] = {
                    "count": len(backups),
                    "latest_path": str(latest),
                    "latest_size_bytes": latest.stat().st_size if latest.exists() else 0,
                }
            else:
                report["backup"] = {"count": 0}
        except Exception:
            report["backup"] = {"count": -1, "error": "unable to check"}

    except Exception:
        report["components"]["database"] = "error"
        report["status"] = "degraded"

    # Embeddings check
    try:
        embeddings = get_embedding_service()
        is_available = embeddings.is_available_sync()
        report["components"]["embeddings"] = "ok" if is_available else "unavailable"
        report["components"]["embedding_model"] = getattr(
            embeddings, "model", "unknown"
        )
    except Exception:
        report["components"]["embeddings"] = "error"

    # Vault sync status
    try:
        from ..services.vault_sync import get_vault_path, get_vault_sync_service
        from ..config import _project_id
        vault_path = get_vault_path(_project_id)
        if vault_path.exists():
            svc = get_vault_sync_service(_project_id, db=_db if '_db' in dir() else None)
            vault_status = svc.get_status()
            report["vault"] = {
                "path": str(vault_path),
                "synced": vault_status.get("synced", False),
                "last_sync": vault_status.get("last_sync"),
            }
        else:
            report["vault"] = {"path": str(vault_path), "synced": False}
    except Exception:
        report["vault"] = {"synced": False, "error": "unable to check"}

    # Scheduler check
    try:
        scheduler = get_scheduler()
        is_running = scheduler.is_running()
        report["components"]["scheduler"] = "running" if is_running else "stopped"
        if is_running:
            report["scheduled_jobs"] = [
                {"id": job.id, "name": job.name, "next_run": str(job.next_run_time)}
                for job in scheduler.get_jobs()
            ]
        if not is_running:
            report["status"] = "degraded"
    except Exception:
        report["components"]["scheduler"] = "error"

    # Loop status files (Proposal 11, E5): last verdict per wrapped daemon job.
    # Purely observational; the overall status is not changed by a flagged job.
    try:
        from ..loops.job_wrapper import default_loops_dir
        from ..loops.status import read_status

        loops = []
        loops_dir = default_loops_dir()
        if loops_dir.exists():
            for status_file in sorted(loops_dir.glob("*_status.md")):
                try:
                    fields, _ = read_status(status_file)
                except Exception:
                    continue
                loops.append(
                    {
                        "job": fields.get("loop_id", status_file.stem.replace("_status", "")),
                        "verified": fields.get("verified"),
                        "verdict": fields.get("checker_verdict"),
                        "updated_at": fields.get("updated_at"),
                    }
                )
        report["loops"] = loops
        report["loops_flagged"] = sum(1 for entry in loops if entry.get("verified") is False)
    except Exception:
        report["loops"] = []
        report["loops_flagged"] = 0

    return report


class HealthCheckHandler(BaseHTTPRequestHandler):
    """HTTP request handler for health checks"""

    def log_message(self, format, *args):
        """Suppress default logging"""
        pass

    def do_GET(self):
        """Handle GET requests"""
        if self.path == "/health" or self.path == "/":
            self._send_health_response()
        elif self.path == "/status":
            self._send_status_response()
        elif self.path == "/stats":
            self._send_stats_response()
        elif self.path == "/flush":
            self._send_flush_response()
        elif self.path == "/briefing":
            self._send_briefing_response()
        else:
            self.send_error(404, "Not Found")

    def do_POST(self):
        """Handle POST requests"""
        if self.path == "/backup":
            self._send_backup_response()
        else:
            self.send_error(405, "Method Not Allowed")

    def _send_backup_response(self):
        """Trigger a database backup and return the path."""
        try:
            db = get_db()
            path = db.backup()
            self._send_json({"status": "ok", "path": str(path)})
        except Exception as e:
            logger.exception("Error triggering backup")
            self._send_json({"status": "error", "message": str(e)}, code=500)

    def _send_json(self, data: dict, code: int = 200):
        """Helper to send a JSON response."""
        body = json.dumps(data).encode()
        self.send_response(code)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(body)

    def _send_health_response(self):
        """Send basic health check response"""
        health = {
            "status": "healthy",
            "timestamp": datetime.utcnow().isoformat(),
            "service": "claudia-memory",
        }

        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(json.dumps(health).encode())

    def _send_status_response(self):
        """Send detailed status response"""
        try:
            status = build_status_report()
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(status).encode())
        except Exception as e:
            logger.exception("Error in status check")
            self.send_error(500, str(e))

    def _send_stats_response(self):
        """Send memory statistics"""
        try:
            db = get_db()

            # Get counts
            memories = db.execute("SELECT COUNT(*) as c FROM memories", fetch=True)
            entities = db.execute("SELECT COUNT(*) as c FROM entities", fetch=True)
            relationships = db.execute("SELECT COUNT(*) as c FROM relationships", fetch=True)
            episodes = db.execute("SELECT COUNT(*) as c FROM episodes", fetch=True)
            patterns = db.execute("SELECT COUNT(*) as c FROM patterns WHERE is_active = 1", fetch=True)
            predictions = db.execute(
                "SELECT COUNT(*) as c FROM predictions WHERE is_shown = 0", fetch=True
            )

            stats = {
                "timestamp": datetime.utcnow().isoformat(),
                "counts": {
                    "memories": memories[0]["c"] if memories else 0,
                    "entities": entities[0]["c"] if entities else 0,
                    "relationships": relationships[0]["c"] if relationships else 0,
                    "episodes": episodes[0]["c"] if episodes else 0,
                    "active_patterns": patterns[0]["c"] if patterns else 0,
                    "pending_predictions": predictions[0]["c"] if predictions else 0,
                },
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(stats).encode())

        except Exception as e:
            logger.exception("Error getting stats")
            self.send_error(500, str(e))

    def _send_flush_response(self):
        """Force WAL checkpoint and return status.

        Called by the PreCompact hook to ensure all buffered data is durably
        written before context compaction occurs.
        """
        try:
            db = get_db()
            # TRUNCATE mode: checkpoint and reset WAL to zero length
            db.execute("PRAGMA wal_checkpoint(TRUNCATE)", fetch=False)

            response = {
                "status": "flushed",
                "timestamp": datetime.utcnow().isoformat(),
                "message": "WAL checkpoint complete",
            }

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())

        except Exception as e:
            logger.exception("Error flushing WAL")
            self.send_error(500, str(e))

    def _send_briefing_response(self):
        """Send compact session briefing (same data as memory_briefing MCP tool).

        Lets session hooks call briefing data over HTTP before MCP tools register,
        providing a pre-MCP layer in the fallback chain.
        """
        try:
            from ..mcp.server import _build_briefing
            briefing_text = _build_briefing()
            response = {"briefing": briefing_text}
            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.end_headers()
            self.wfile.write(json.dumps(response).encode())
        except Exception as e:
            logger.exception("Error building briefing")
            self.send_error(500, str(e))


class HealthServer:
    """HTTP server for health checks"""

    def __init__(self, port: int = None):
        self.port = port or get_config().health_port
        self.server: Optional[HTTPServer] = None
        self._thread: Optional[threading.Thread] = None
        self._running = False

    def start(self) -> None:
        """Start the health check server in a background thread"""
        if self._running:
            logger.warning("Health server already running")
            return

        try:
            self.server = HTTPServer(("localhost", self.port), HealthCheckHandler)
            self._thread = threading.Thread(target=self._serve, daemon=True)
            self._thread.start()
            self._running = True
            logger.info(f"Health server started on port {self.port}")
        except OSError as e:
            import errno as _errno
            if e.errno == _errno.EADDRINUSE:
                logger.error(
                    f"Port {self.port} is already in use. "
                    "Another Claudia daemon is likely running -- this causes database corruption. "
                    "Stop the existing daemon before starting a new one."
                )
            else:
                logger.error(f"Failed to start health server on port {self.port}: {e}")
            raise
        except Exception as e:
            logger.exception(f"Failed to start health server: {e}")
            raise

    def _serve(self) -> None:
        """Serve requests"""
        if self.server:
            self.server.serve_forever()

    def stop(self) -> None:
        """Stop the health check server"""
        if self.server:
            self.server.shutdown()
            self.server = None
        self._running = False
        logger.info("Health server stopped")

    def is_running(self) -> bool:
        """Check if server is running"""
        return self._running


# Global health server instance
_health_server: Optional[HealthServer] = None


def get_health_server() -> HealthServer:
    """Get or create the global health server"""
    global _health_server
    if _health_server is None:
        _health_server = HealthServer()
    return _health_server


def start_health_server() -> None:
    """Start the global health server"""
    get_health_server().start()


def stop_health_server() -> None:
    """Stop the global health server"""
    if _health_server:
        _health_server.stop()
