"""HTTP server for trajectory viewer using Python stdlib."""

from __future__ import annotations

import json
import logging
import re
import sys
import webbrowser
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
from typing import Any
from urllib.parse import unquote, urlparse

from harbor.viewer.scanner import DirectoryScanner

logger = logging.getLogger(__name__)


class TrajectoryHandler(BaseHTTPRequestHandler):
    """HTTP request handler for trajectory viewer API."""

    scanner: DirectoryScanner | None = None

    def log_message(self, format: str, *args: Any) -> None:
        """Override to use logging module instead of stderr."""
        logger.debug("%s - %s", self.address_string(), format % args)

    def do_GET(self) -> None:
        """Handle GET requests with URL routing."""
        path = urlparse(self.path).path

        # Route matching
        if path == "/":
            self._serve_index()
        elif path == "/api/jobs":
            self._handle_list_jobs()
        elif match := re.match(r"^/api/jobs/([^/]+)/trials$", path):
            self._handle_list_trials(unquote(match.group(1)))
        elif match := re.match(r"^/api/jobs/([^/]+)/config$", path):
            self._handle_get_config(unquote(match.group(1)))
        elif match := re.match(r"^/api/jobs/([^/]+)/log$", path):
            self._handle_get_job_log(unquote(match.group(1)))
        elif match := re.match(r"^/api/trajectories/([^/]+)/([^/]+)$", path):
            self._handle_get_trajectory(
                unquote(match.group(1)), unquote(match.group(2))
            )
        elif match := re.match(r"^/api/trajectories/([^/]+)/([^/]+)/mtime$", path):
            self._handle_get_mtime(unquote(match.group(1)), unquote(match.group(2)))
        elif match := re.match(r"^/static/([^/]+)$", path):
            self._serve_static(unquote(match.group(1)))
        else:
            self._send_not_found()

    def _send_json(self, data: Any, status: int = 200) -> None:
        """Send JSON response with proper headers."""
        body = json.dumps(data).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

    def _send_not_found(self) -> None:
        """Send 404 response."""
        self._send_json({"error": "Not found"}, 404)

    def _serve_index(self) -> None:
        """Serve the viewer.html file."""
        html_path = Path(__file__).parent / "templates" / "viewer.html"
        content = html_path.read_bytes()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(content)))
        self.end_headers()
        self.wfile.write(content)

    def _handle_list_jobs(self) -> None:
        """GET /api/jobs - List all jobs."""
        if self.scanner is None:
            self._send_json({"error": "Scanner not initialized"}, 500)
            return

        jobs = self.scanner.scan_jobs()
        data = [j.model_dump(exclude={"job_path"}) for j in jobs]
        self._send_json(data)

    def _handle_list_trials(self, job_id: str) -> None:
        """GET /api/jobs/{job_id}/trials - List trials for a job."""
        if self.scanner is None:
            self._send_json({"error": "Scanner not initialized"}, 500)
            return

        trials = self.scanner.scan_trials(job_id)
        data = [t.model_dump(exclude={"trial_path"}) for t in trials]
        self._send_json(data)

    def _handle_get_config(self, job_id: str) -> None:
        """GET /api/jobs/{job_id}/config - Get job config.json."""
        if self.scanner is None:
            self._send_json({"error": "Scanner not initialized"}, 500)
            return

        job_path = self.scanner._resolve_job_path(job_id)
        if not job_path:
            self._send_json(None)
            return

        config_path = job_path / "config.json"
        if config_path.exists():
            try:
                self._send_json(json.loads(config_path.read_text()))
                return
            except Exception:
                pass
        self._send_json(None)

    def _handle_get_job_log(self, job_id: str) -> None:
        """GET /api/jobs/{job_id}/log - Get job.log content."""
        if self.scanner is None:
            self._send_json({"error": "Scanner not initialized"}, 500)
            return

        job_path = self.scanner._resolve_job_path(job_id)
        if not job_path:
            self._send_json({"error": "Job not found"}, 404)
            return

        log_path = job_path / "job.log"
        if log_path.exists():
            try:
                content = log_path.read_text(errors="replace")
                self._send_json({"content": content})
                return
            except Exception:
                pass
        self._send_json({"content": None})

    def _handle_get_trajectory(self, job_id: str, trial_id: str) -> None:
        """GET /api/trajectories/{job_id}/{trial_id} - Get full trajectory data."""
        if self.scanner is None:
            self._send_json({"error": "Scanner not initialized"}, 500)
            return

        data = self.scanner.get_trajectory_data(job_id, trial_id)
        if data is None:
            self._send_json({"error": "Trajectory not found"}, 404)
            return

        self._send_json(data.model_dump())

    def _handle_get_mtime(self, job_id: str, trial_id: str) -> None:
        """GET /api/trajectories/{job_id}/{trial_id}/mtime - Get modification time."""
        if self.scanner is None:
            self._send_json({"error": "Scanner not initialized"}, 500)
            return

        mtime = self.scanner.get_trajectory_mtime(job_id, trial_id)
        self._send_json({"mtime": mtime})

    def _serve_static(self, filename: str) -> None:
        """Serve static files (vendored JS/CSS dependencies)."""
        static_dir = Path(__file__).parent / "static"
        file_path = static_dir / filename

        # Security: ensure file is within static directory
        try:
            file_path = file_path.resolve()
            if not file_path.is_relative_to(static_dir.resolve()):
                self._send_not_found()
                return
        except (ValueError, RuntimeError):
            self._send_not_found()
            return

        if not file_path.exists() or not file_path.is_file():
            self._send_not_found()
            return

        # Determine content type
        content_type = "application/octet-stream"
        if filename.endswith(".js"):
            content_type = "application/javascript; charset=utf-8"
        elif filename.endswith(".css"):
            content_type = "text/css; charset=utf-8"

        content = file_path.read_bytes()
        self.send_response(200)
        self.send_header("Content-Type", content_type)
        self.send_header("Content-Length", str(len(content)))
        self.end_headers()
        self.wfile.write(content)


def start_server(
    folder: Path,
    host: str = "127.0.0.1",
    port: int = 8080,
    open_browser: bool = True,
) -> None:
    """Start the trajectory viewer server.

    Args:
        folder: Directory to scan for jobs/trials with trajectories.
        host: Host to bind the server to.
        port: Port to run the server on.
        open_browser: Whether to open a browser automatically.
    """
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s - %(levelname)s - %(message)s",
        datefmt="%H:%M:%S",
    )

    if not folder.exists():
        logger.error(f"Folder not found: {folder}")
        sys.exit(1)

    # Initialize scanner on the handler class
    TrajectoryHandler.scanner = DirectoryScanner(folder)

    # Scan and show summary
    jobs = TrajectoryHandler.scanner.scan_jobs()
    total_trials = sum(j.trial_count for j in jobs)

    url = f"http://{host}:{port}"
    print("Harbor Viewer")
    print(f"  URL: {url}")
    print(f"  Watching: {folder}")
    print(f"  Found: {len(jobs)} job(s), {total_trials} trial(s)")
    print("Press Ctrl+C to stop\n")

    if open_browser:
        webbrowser.open(url)

    # Run HTTP server
    server = HTTPServer((host, port), TrajectoryHandler)
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        print("\nShutting down...")
        server.shutdown()
