# -*- coding: utf-8 -*-
"""5초 확인 로컬 웹 (127.0.0.1 전용, 외부 노출 없음).

메일의 확인 링크 → GET /ack?id=..&by=.. → 폼 → POST → acks 저장.
표준 http.server만 사용 (기술 스택 고정). DB(state.db) 외에는 아무것도 쓰지 않는다.
"""

from __future__ import annotations

import hmac
import html
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import parse_qs, urlparse

from watcher.state import KIND_LABEL as _KIND_LABEL
from watcher.state import State

_PAGE = """<!DOCTYPE html>
<html lang="ko"><head><meta charset="utf-8"><title>변경 확인</title></head>
<body style="font-family:'Malgun Gothic',Segoe UI,sans-serif;max-width:560px;
             margin:40px auto;color:#1f2328">
{body}
</body></html>"""

def _form(change, by: str, token: str) -> str:
    return f"""
  <h2>변경 확인</h2>
  <p style="background:#f6f8fa;padding:10px 14px;border-radius:6px">
    <strong>[{_KIND_LABEL.get(change.kind, change.kind)}]</strong>
    {html.escape(change.path)}<br>
    <span style="color:#57606a">변경 번호 #{change.id}
    {f' · 프로젝트 {html.escape(change.project)}' if change.project else ''}</span></p>
  <form method="post" action="/ack">
    <input type="hidden" name="id" value="{change.id}">
    <input type="hidden" name="t" value="{html.escape(token)}">
    <p>확인자<br><input name="by" value="{html.escape(by)}" required
        style="width:100%;padding:6px"></p>
    <p>한 줄 메모 (선택)<br><input name="note" style="width:100%;padding:6px"
        placeholder="예: 팀에 공유 완료 / 재검토 필요"></p>
    <button type="submit" style="padding:8px 20px;background:#1a7f37;color:#fff;
        border:none;border-radius:6px;font-size:15px">확인함</button>
  </form>"""


def make_handler(db_path: Path):
    class AckHandler(BaseHTTPRequestHandler):
        def _send(self, code: int, body: str, set_cookie: str | None = None) -> None:
            data = _PAGE.format(body=body).encode("utf-8")
            self.send_response(code)
            self.send_header("Content-Type", "text/html; charset=utf-8")
            self.send_header("Content-Length", str(len(data)))
            if set_cookie:
                self.send_header("Set-Cookie", set_cookie)
            self.end_headers()
            self.wfile.write(data)

        def _saved_name(self) -> str:
            """최근 확인자 이름(쿠키) — 매번 재입력하지 않게 (2026-08-26)."""
            from http.cookies import SimpleCookie
            from urllib.parse import unquote
            c = SimpleCookie(self.headers.get("Cookie", ""))
            return unquote(c["ack_by"].value) if "ack_by" in c else ""

        def do_GET(self) -> None:
            url = urlparse(self.path)
            if url.path != "/ack":
                self._send(404, "<p>없는 주소입니다.</p>")
                return
            q = parse_qs(url.query)
            try:
                change_id = int(q.get("id", [""])[0])
            except ValueError:
                self._send(400, "<p>잘못된 변경 번호입니다.</p>")
                return
            token = q.get("t", [""])[0]
            with State(db_path) as state:
                change = state.get_change(change_id)
                expected = state.ack_token(change_id)
            if change is None:
                self._send(404, f"<p>변경 #{change_id}을 찾을 수 없습니다.</p>")
                return
            # 브리핑 링크에만 있는 토큰 검증 — 타 사이트가 확인을 위조하지 못하게 (CSRF)
            if not hmac.compare_digest(token, expected):
                self._send(403, "<p>유효하지 않은 링크입니다. 브리핑 메일의 링크로 접속해주세요.</p>")
                return
            self._send(200, _form(change,
                                  q.get("by", [""])[0] or self._saved_name(), token))

        def do_POST(self) -> None:
            if urlparse(self.path).path != "/ack":
                self._send(404, "<p>없는 주소입니다.</p>")
                return
            length = int(self.headers.get("Content-Length", 0))
            q = parse_qs(self.rfile.read(length).decode("utf-8"))
            try:
                change_id = int(q.get("id", [""])[0])
            except ValueError:
                self._send(400, "<p>잘못된 변경 번호입니다.</p>")
                return
            by = q.get("by", [""])[0].strip()
            note = q.get("note", [""])[0].strip()
            token = q.get("t", [""])[0]
            if not by:
                self._send(400, "<p>확인자 이름이 필요합니다.</p>")
                return
            with State(db_path) as state:
                if not hmac.compare_digest(token, state.ack_token(change_id)):
                    self._send(403, "<p>유효하지 않은 요청입니다.</p>")
                    return
                ok = state.add_ack(change_id, by, note)
            if not ok:
                self._send(404, f"<p>변경 #{change_id}을 찾을 수 없습니다.</p>")
                return
            from urllib.parse import quote
            self._send(200, f"""
  <h2 style="color:#1a7f37">확인 저장 완료</h2>
  <p>변경 #{change_id} — {html.escape(by)}님의 확인이 기록되었습니다.
  {f'<br>메모: {html.escape(note)}' if note else ''}</p>
  <p style="color:#57606a">이 창은 닫으셔도 됩니다.</p>""",
                set_cookie=(f"ack_by={quote(by)}; Path=/; "
                            f"Max-Age=31536000; SameSite=Strict"))

        def log_message(self, fmt, *args):  # 콘솔 소음 억제
            pass

    return AckHandler


def make_server(db_path: Path, host: str = "127.0.0.1", port: int = 8765) -> ThreadingHTTPServer:
    return ThreadingHTTPServer((host, port), make_handler(Path(db_path)))


def serve(db_path: Path, host: str = "127.0.0.1", port: int = 8765) -> None:
    server = make_server(db_path, host, port)
    print(f"확인 서버 시작: http://{host}:{server.server_address[1]} (Ctrl+C로 종료)")
    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()
