"""FastAPI-Einstieg: Upload, Verarbeitung, Download, Health."""

from __future__ import annotations

import logging
import shutil
import tempfile
import threading
import uuid
from contextlib import asynccontextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta
from pathlib import Path

from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from starlette.background import BackgroundTask

from app.config import (
    ALLOWED_SOURCE_SUFFIXES,
    ALLOWED_TEMPLATE_SUFFIXES,
    FRONTEND_DIR,
    KEEP_OUTPUT,
    MAX_UPLOAD_BYTES,
    MAX_UPLOAD_MB,
    OUTPUT_DIR,
    OUTPUT_TTL_SECONDS,
    ensure_directories,
)
from app.processor import process_import

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logger = logging.getLogger("kundenimport.api")


@dataclass
class StoredOutput:
    path: Path
    created_at: datetime


_STORE: dict[str, StoredOutput] = {}
_LOCK = threading.Lock()


def _purge_expired() -> None:
    now = datetime.now()
    expired: list[str] = []
    with _LOCK:
        for key, item in _STORE.items():
            if now - item.created_at > timedelta(seconds=OUTPUT_TTL_SECONDS):
                expired.append(key)
        for key in expired:
            item = _STORE.pop(key)
            if not KEEP_OUTPUT:
                item.path.unlink(missing_ok=True)


def _store_output(path: Path) -> str:
    _purge_expired()
    download_id = uuid.uuid4().hex
    with _LOCK:
        _STORE[download_id] = StoredOutput(path=path, created_at=datetime.now())
    return download_id


def _pop_output(download_id: str) -> Path | None:
    _purge_expired()
    with _LOCK:
        item = _STORE.get(download_id)
        if item is None:
            return None
        if not KEEP_OUTPUT:
            _STORE.pop(download_id, None)
        return item.path


@asynccontextmanager
async def lifespan(_app: FastAPI):
    ensure_directories()
    yield
    if not KEEP_OUTPUT:
        with _LOCK:
            for item in _STORE.values():
                item.path.unlink(missing_ok=True)
            _STORE.clear()


app = FastAPI(title="Kundenimport", version="1.0.0", lifespan=lifespan)
app.mount("/static", StaticFiles(directory=FRONTEND_DIR), name="static")


@app.get("/health")
def health() -> dict[str, str]:
    return {"status": "ok"}


@app.get("/")
def index() -> FileResponse:
    return FileResponse(FRONTEND_DIR / "index.html")


def _suffix_ok(filename: str | None, allowed: set[str]) -> bool:
    if not filename:
        return False
    return Path(filename).suffix.lower() in allowed


async def _save_upload(upload: UploadFile, dest: Path) -> None:
    size = 0
    with dest.open("wb") as handle:
        while True:
            chunk = await upload.read(1024 * 1024)
            if not chunk:
                break
            size += len(chunk)
            if size > MAX_UPLOAD_BYTES:
                handle.close()
                dest.unlink(missing_ok=True)
                raise HTTPException(
                    status_code=413,
                    detail=f"Die Datei ist zu groß. Maximal {MAX_UPLOAD_MB} MB sind erlaubt.",
                )
            handle.write(chunk)
    if size == 0:
        dest.unlink(missing_ok=True)
        raise HTTPException(status_code=400, detail="Die hochgeladene Datei ist leer.")


@app.post("/process")
async def process(
    table_a: UploadFile = File(...),
    template_b: UploadFile = File(...),
) -> dict:
    if not _suffix_ok(table_a.filename, ALLOWED_SOURCE_SUFFIXES):
        raise HTTPException(
            status_code=400,
            detail="Tabelle A konnte nicht erkannt werden.",
        )
    if not _suffix_ok(template_b.filename, ALLOWED_TEMPLATE_SUFFIXES):
        raise HTTPException(
            status_code=400,
            detail="Vorlage B ist nicht kompatibel.",
        )

    temp_dir = Path(tempfile.mkdtemp(prefix="kundenimport_"))
    path_a = temp_dir / f"a{Path(table_a.filename or 'a.xlsx').suffix.lower()}"
    path_b = temp_dir / f"b{Path(template_b.filename or 'b.xlsx').suffix.lower()}"

    try:
        await _save_upload(table_a, path_a)
        await _save_upload(template_b, path_b)
        result = process_import(path_a, path_b, OUTPUT_DIR)
    finally:
        shutil.rmtree(temp_dir, ignore_errors=True)
        await table_a.close()
        await template_b.close()

    payload: dict = {
        "success": result.success,
        "message": result.message,
        "stats": result.stats,
        "issues": result.issues,
        "log": result.log,
    }

    if result.success and result.output_path:
        download_id = _store_output(Path(result.output_path))
        payload["download_id"] = download_id
        payload["download_url"] = f"/download/{download_id}"
        payload["filename"] = "FINAL_Import.xlsx"
    else:
        if result.output_path:
            Path(result.output_path).unlink(missing_ok=True)

    return payload


@app.get("/download/{download_id}")
def download(download_id: str) -> FileResponse:
    path = _pop_output(download_id)
    if path is None or not path.exists():
        raise HTTPException(status_code=404, detail="Die Datei ist nicht mehr verfügbar.")

    def _cleanup() -> None:
        if not KEEP_OUTPUT:
            path.unlink(missing_ok=True)

    return FileResponse(
        path,
        filename="FINAL_Import.xlsx",
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        background=BackgroundTask(_cleanup),
    )
