from __future__ import annotations

from datetime import datetime
from pathlib import Path
import shutil
import uuid
from typing import Callable

from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi.responses import HTMLResponse


def register_seatmap_routes(
    app: FastAPI,
    *,
    upload_dir: Path,
    get_conn,
    seat_map_payload_cls,
    seat_layout_payload_cls,
    fixed_office_source_key: str,
    parse_dxf_layout: Callable[[Path], tuple[dict[str, object], list[dict[str, object]]]],
    serialize_seat_map_payload: Callable[[object], tuple[object, ...]],
    fetch_seat_layout: Callable[[int, object], dict[str, object]],
    parse_as_of: Callable[[str | None], object],
    ensure_fixed_office_seat_map: Callable[[str, bool], dict[str, object] | None],
    build_center_chair_viewer_html: Callable[[dict[str, object]], str],
    save_seat_layout: Callable[[int, object], list[dict[str, object]]],
) -> None:
    @app.post("/api/uploads/profile-photo")
    def upload_profile_photo(file: UploadFile = File(...), member_name: str = Form("")) -> dict[str, str]:
        suffix = Path(file.filename or "").suffix.lower()
        if suffix not in {".png", ".jpg", ".jpeg", ".webp", ".gif"}:
            raise HTTPException(status_code=400, detail="Only image files are allowed.")
        stem = member_name.strip().replace(" ", "-") or "member"
        filename = f"{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{stem}-{uuid.uuid4().hex[:8]}{suffix}"
        target = upload_dir / filename
        with target.open("wb") as out_file:
            shutil.copyfileobj(file.file, out_file)
        return {"url": f"/uploads/{filename}"}

    @app.post("/api/uploads/seat-map-image")
    def upload_seat_map_image(file: UploadFile = File(...), seat_map_name: str = Form("")) -> dict[str, str]:
        suffix = Path(file.filename or "").suffix.lower()
        if suffix not in {".png", ".jpg", ".jpeg", ".webp", ".gif"}:
            raise HTTPException(status_code=400, detail="Only image files are allowed.")
        stem = seat_map_name.strip().replace(" ", "-") or "seat-map"
        filename = f"seat-map-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{stem}-{uuid.uuid4().hex[:8]}{suffix}"
        target = upload_dir / filename
        with target.open("wb") as out_file:
            shutil.copyfileobj(file.file, out_file)
        return {"url": f"/uploads/{filename}"}

    @app.post("/api/seat-maps/dxf")
    async def create_dxf_seat_map(file: UploadFile = File(...), name: str = Form(...)) -> dict[str, object]:
        suffix = Path(file.filename or "").suffix.lower()
        if suffix != ".dxf":
            raise HTTPException(status_code=400, detail="DXF 파일만 업로드할 수 있습니다.")

        stem = name.strip().replace(" ", "-") or "seat-map"
        filename = f"seat-map-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{stem}-{uuid.uuid4().hex[:8]}{suffix}"
        target = upload_dir / filename
        content = await file.read()
        with target.open("wb") as out_file:
            out_file.write(content)

        metadata, slots = parse_dxf_layout(target)

        payload = seat_map_payload_cls(
            name=name.strip(),
            source_type="dxf",
            source_url=f"/uploads/{filename}",
            image_url="",
            preview_svg=metadata["preview_svg"],
            view_box_min_x=metadata["view_box_min_x"],
            view_box_min_y=metadata["view_box_min_y"],
            view_box_width=metadata["view_box_width"],
            view_box_height=metadata["view_box_height"],
            image_width=None,
            image_height=None,
            grid_rows=1,
            grid_cols=1,
            cell_gap=0,
            is_active=True,
        )

        with get_conn() as conn:
            with conn.cursor() as cur:
                cur.execute("UPDATE seat_maps SET is_active = FALSE, updated_at = NOW() WHERE is_active = TRUE")
                cur.execute(
                    """
                    INSERT INTO seat_maps (
                        name, source_type, source_url, preview_svg,
                        view_box_min_x, view_box_min_y, view_box_width, view_box_height,
                        image_url, image_width, image_height, grid_rows, grid_cols, cell_gap, is_active
                    )
                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                    RETURNING id, name, source_type, source_url, preview_svg,
                              view_box_min_x, view_box_min_y, view_box_width, view_box_height,
                              image_url, image_width, image_height, grid_rows, grid_cols,
                              cell_gap, is_active, created_at, updated_at
                    """,
                    serialize_seat_map_payload(payload),
                )
                seat_map = cur.fetchone()
                for slot in slots:
                    cur.execute(
                        """
                        INSERT INTO seat_slots (seat_map_id, slot_key, label, x, y, rotation, layer_name)
                        VALUES (%s, %s, %s, %s, %s, %s, %s)
                        """,
                        (
                            seat_map["id"],
                            slot["slot_key"],
                            slot["label"],
                            slot["x"],
                            slot["y"],
                            slot["rotation"],
                            slot["layer_name"],
                        ),
                    )
            conn.commit()

        return fetch_seat_layout(int(seat_map["id"]))

    @app.get("/api/seat-maps/active")
    def get_active_seat_map(office_key: str | None = None) -> dict[str, dict[str, object]]:
        requested_key = (office_key or "").strip() or fixed_office_source_key
        seat_map = ensure_fixed_office_seat_map(requested_key, activate=requested_key == fixed_office_source_key)
        if seat_map is None:
            raise HTTPException(status_code=404, detail="Active seat map not found.")
        return {"item": seat_map}

    @app.post("/api/seat-maps")
    def create_seat_map(payload: seat_map_payload_cls) -> dict[str, dict[str, object]]:
        with get_conn() as conn:
            with conn.cursor() as cur:
                if payload.is_active:
                    cur.execute("UPDATE seat_maps SET is_active = FALSE, updated_at = NOW() WHERE is_active = TRUE")
                cur.execute(
                    """
                    INSERT INTO seat_maps (
                        name, source_type, source_url, preview_svg,
                        view_box_min_x, view_box_min_y, view_box_width, view_box_height,
                        image_url, image_width, image_height, grid_rows, grid_cols, cell_gap, is_active
                    )
                    VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
                    RETURNING id, name, source_type, source_url, preview_svg,
                              view_box_min_x, view_box_min_y, view_box_width, view_box_height,
                              image_url, image_width, image_height, grid_rows, grid_cols,
                              cell_gap, is_active, created_at, updated_at
                    """,
                    serialize_seat_map_payload(payload),
                )
                seat_map = cur.fetchone()
            conn.commit()
        return {"item": seat_map}

    @app.put("/api/seat-maps/{seat_map_id}")
    def update_seat_map(seat_map_id: int, payload: seat_map_payload_cls) -> dict[str, dict[str, object]]:
        with get_conn() as conn:
            with conn.cursor() as cur:
                if payload.source_type != "dxf":
                    cur.execute(
                        """
                        SELECT COUNT(*) AS count
                        FROM seat_positions
                        WHERE seat_map_id = %s
                          AND (row_index >= %s OR col_index >= %s)
                        """,
                        (seat_map_id, payload.grid_rows, payload.grid_cols),
                    )
                    out_of_bounds_count = int(cur.fetchone()["count"])
                    if out_of_bounds_count > 0:
                        raise HTTPException(status_code=400, detail="현재 배치된 좌석이 새 그리드 범위를 벗어납니다. 먼저 좌석 배치를 정리하세요.")
                if payload.is_active:
                    cur.execute("UPDATE seat_maps SET is_active = FALSE, updated_at = NOW() WHERE is_active = TRUE AND id <> %s", (seat_map_id,))
                cur.execute(
                    """
                    UPDATE seat_maps
                    SET name = %s,
                        source_type = %s,
                        source_url = %s,
                        preview_svg = %s,
                        view_box_min_x = %s,
                        view_box_min_y = %s,
                        view_box_width = %s,
                        view_box_height = %s,
                        image_url = %s,
                        image_width = %s,
                        image_height = %s,
                        grid_rows = %s,
                        grid_cols = %s,
                        cell_gap = %s,
                        is_active = %s,
                        updated_at = NOW()
                    WHERE id = %s
                    RETURNING id, name, source_type, source_url, preview_svg,
                              view_box_min_x, view_box_min_y, view_box_width, view_box_height,
                              image_url, image_width, image_height, grid_rows, grid_cols,
                              cell_gap, is_active, created_at, updated_at
                    """,
                    (*serialize_seat_map_payload(payload), seat_map_id),
                )
                seat_map = cur.fetchone()
                if seat_map is None:
                    raise HTTPException(status_code=404, detail="Seat map not found.")
            conn.commit()
        return {"item": seat_map}

    @app.get("/api/seat-maps/{seat_map_id}/layout")
    def get_seat_layout(seat_map_id: int, as_of: str | None = None) -> dict[str, object]:
        return fetch_seat_layout(seat_map_id, parse_as_of(as_of))

    @app.get("/api/seat-maps/{seat_map_id}/viewer")
    def get_seat_map_viewer(seat_map_id: int, as_of: str | None = None) -> HTMLResponse:
        layout = fetch_seat_layout(seat_map_id, parse_as_of(as_of))
        seat_map = layout.get("seat_map") or {}
        if seat_map.get("source_type") not in {"dxf", "fixed_html"}:
            raise HTTPException(status_code=400, detail="Viewer is only available for supported seat maps.")
        return HTMLResponse(build_center_chair_viewer_html(layout))

    @app.put("/api/seat-maps/{seat_map_id}/layout")
    def update_seat_layout(seat_map_id: int, payload: seat_layout_payload_cls) -> dict[str, list[dict[str, object]]]:
        return {"items": save_seat_layout(seat_map_id, payload)}
