#!/usr/bin/env python3
"""Lokální přehled zájezdů: statika + JSON API nad SQLite."""

from __future__ import annotations

import hmac
import json
import re
from http.cookies import SimpleCookie
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse

from db import DB_PATH, connect, init_db
from scrape import SNAPSHOT_PATH, build_snapshot, load_config, run as run_scrape

ROOT = Path(__file__).resolve().parent
STATIC = ROOT / "static"
HOST = "0.0.0.0"
PORT = 8765


def json_bytes(payload, status=200):
    body = json.dumps(payload, ensure_ascii=False, default=str).encode("utf-8")
    return status, body


def expected_token() -> str:
    return str(load_config().get("access_token") or "bagr")


def tokens_match(got: str | None, expected: str) -> bool:
    if got is None:
        return False
    a, b = str(got), str(expected)
    if len(a) != len(b):
        return False
    return hmac.compare_digest(a, b)


class Handler(SimpleHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    _grant_cookie = False

    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(STATIC), **kwargs)

    def log_message(self, fmt, *args):
        msg = fmt % args
        msg = re.sub(r"token=[^&\s]+", "token=***", msg)
        print(f"[http] {self.address_string()} {msg}")

    def end_headers(self):
        self.send_header("Cache-Control", "no-store")
        if self._grant_cookie:
            token = expected_token()
            self.send_header(
                "Set-Cookie",
                f"token={token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000",
            )
        super().end_headers()

    def _path(self):
        parsed = urlparse(self.path)
        raw = parsed.path or "/"
        if raw.startswith("//"):
            raw = "/" + raw.lstrip("/")
        return parsed, raw

    def _authorized(self, parsed) -> bool:
        expected = expected_token()
        qs_token = (parse_qs(parsed.query).get("token") or [None])[0]
        if tokens_match(qs_token, expected):
            self._grant_cookie = True
            return True
        cookie = SimpleCookie()
        try:
            cookie.load(self.headers.get("Cookie") or "")
        except Exception:
            return False
        morsel = cookie.get("token")
        if morsel is None:
            return False
        return tokens_match(morsel.value, expected)

    def _deny(self):
        self.send_error(404)

    def do_GET(self):
        parsed, raw = self._path()
        if not self._authorized(parsed):
            return self._deny()
        if raw in ("/", "/index.html"):
            self.path = "/index.html"
            return SimpleHTTPRequestHandler.do_GET(self)
        if raw.startswith("/api/"):
            return self.handle_api(parsed, raw)
        self.path = raw + (("?" + parsed.query) if parsed.query else "")
        return SimpleHTTPRequestHandler.do_GET(self)

    def do_POST(self):
        parsed, raw = self._path()
        if not self._authorized(parsed):
            return self._deny()
        if raw == "/api/scrape":
            try:
                run_scrape()
                status, body = json_bytes({"ok": True, "overview": load_overview()})
            except Exception as exc:  # noqa: BLE001
                status, body = json_bytes({"ok": False, "error": str(exc)}, 500)
            return self.respond(status, body, "application/json")
        self.send_error(404)

    def handle_api(self, parsed, raw=None):
        qs = parse_qs(parsed.query)
        path = raw or parsed.path
        try:
            if path == "/api/overview":
                status, body = json_bytes(load_overview())
            elif path == "/api/history":
                hotel_id = int((qs.get("hotel_id") or [0])[0])
                status, body = json_bytes(load_history(hotel_id))
            elif path == "/api/status":
                status, body = json_bytes(load_status())
            else:
                status, body = json_bytes({"error": "not found"}, 404)
        except Exception as exc:  # noqa: BLE001
            status, body = json_bytes({"error": str(exc)}, 500)
        self.respond(status, body, "application/json")

    def respond(self, status, body, content_type):
        self.send_response(status)
        self.send_header("Content-Type", f"{content_type}; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)


def load_overview() -> dict:
    if SNAPSHOT_PATH.exists():
        return json.loads(SNAPSHOT_PATH.read_text(encoding="utf-8"))
    if not DB_PATH.exists():
        cfg = load_config()
        return {"generated_at": None, "filters": public_safe(cfg), "last_scrape": None, "hotels": [], "flights": []}
    conn = connect()
    data = build_snapshot(conn)
    conn.close()
    return data


def public_safe(cfg: dict) -> dict:
    skip = {"email", "access_token", "user_agent"}
    return {k: v for k, v in cfg.items() if k not in skip}


def load_history(hotel_id: int) -> dict:
    conn = connect()
    rows = conn.execute(
        """
        SELECT * FROM price_history
        WHERE hotel_id=?
        ORDER BY scraped_at, id
        """,
        (hotel_id,),
    ).fetchall()
    conn.close()
    return {"hotel_id": hotel_id, "points": [dict(r) for r in rows]}


def load_status() -> dict:
    cfg = load_config()
    conn = init_db()
    last = conn.execute("SELECT * FROM scrapes ORDER BY id DESC LIMIT 1").fetchone()
    hotels = conn.execute("SELECT COUNT(*) AS n FROM hotels").fetchone()["n"]
    flights = conn.execute("SELECT COUNT(*) AS n FROM flights").fetchone()["n"]
    history = conn.execute("SELECT COUNT(*) AS n FROM price_history").fetchone()["n"]
    conn.close()
    return {
        "filters": {
            "date_from": cfg["date_from"],
            "date_to": cfg["date_to"],
            "departure_airport": cfg["departure_airport"],
            "meal_filter": cfg["meal_filter"],
        },
        "db": str(DB_PATH),
        "hotels": hotels,
        "flights": flights,
        "history_points": history,
        "last_scrape": dict(last) if last else None,
    }


def main():
    STATIC.mkdir(exist_ok=True)
    init_db().close()
    httpd = ThreadingHTTPServer((HOST, PORT), Handler)
    print(f"Přehled běží na http://127.0.0.1:{PORT}/")
    httpd.serve_forever()


if __name__ == "__main__":
    main()
