"""REST API mesin deteksi (FastAPI). Jalankan: uvicorn app.main:app --host 127.0.0.1 --port 8000"""
from __future__ import annotations

import asyncio
import hashlib
import hmac
import logging
from contextlib import asynccontextmanager
from typing import Any, Literal

from fastapi import Depends, FastAPI, Header, HTTPException, Request
from pydantic import BaseModel, Field

from .config import env, load_env
from .engine import Engine
from .notifier import send_alert_email, smtp_enabled
from .repository import MySQLRepository, Repository

load_env()
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(levelname)s %(message)s")
log = logging.getLogger("csm.api")

MAX_EVENTS_PER_BATCH = 500
MAX_BODY_BYTES = 1_000_000


class SensorEvent(BaseModel):
    type: Literal["http_request", "login", "firewall", "metrics", "file_integrity",
                  "syslog", "admin_action", "heartbeat"]
    ip: str | None = Field(default=None, max_length=45)
    method: str | None = Field(default=None, max_length=10)
    path: str | None = Field(default=None, max_length=2000)
    query: str | None = Field(default=None, max_length=4000)
    body: str | None = Field(default=None, max_length=4000)
    user_agent: str | None = Field(default=None, max_length=255)
    username: str | None = Field(default=None, max_length=100)
    success: bool | None = None
    dst_port: int | None = Field(default=None, ge=0, le=65535)
    metrics: dict[str, float | None] | None = None
    services: dict[str, str] | None = None
    files: list[dict[str, Any]] | None = None
    message: str | None = Field(default=None, max_length=2000)
    action: str | None = Field(default=None, max_length=200)
    os: str | None = Field(default=None, max_length=30)


class IngestRequest(BaseModel):
    sensor_code: str = Field(min_length=1, max_length=50)
    hostname: str | None = Field(default=None, max_length=100)
    os: str | None = Field(default=None, max_length=30)
    events: list[SensorEvent] = Field(default_factory=list, max_length=MAX_EVENTS_PER_BATCH)


def build_repo() -> Repository:
    return MySQLRepository(env("DB_HOST", "127.0.0.1"), int(env("DB_PORT", "3306")), env("DB_USER", "root"),
                           env("DB_PASS", ""), env("DB_NAME", "cybersecurity_monitoring"))


def create_app(repo: Repository | None = None, start_scheduler: bool = True) -> FastAPI:
    repo = repo or build_repo()
    engine = Engine(repo)

    async def scheduler():
        interval = max(5, int(env("ENGINE_SCHEDULER_SECONDS", "30")))
        ticks = 0
        while True:
            await asyncio.sleep(interval)
            try:
                await asyncio.to_thread(engine.run_scheduled)
                await asyncio.to_thread(email_pending, repo)
                ticks += 1
                if ticks % max(1, 86400 // interval) == 0:
                    await asyncio.to_thread(engine.purge)
            except Exception:
                log.exception("Pemeriksaan terjadwal gagal")

    @asynccontextmanager
    async def lifespan(_: FastAPI):
        task = asyncio.create_task(scheduler()) if start_scheduler else None
        yield
        if task:
            task.cancel()

    app = FastAPI(title="Cybersecurity Monitoring - Detection Engine", version="1.0.0",
                  docs_url="/docs" if env("APP_ENV") != "production" else None, redoc_url=None,
                  openapi_url="/openapi.json" if env("APP_ENV") != "production" else None, lifespan=lifespan)
    app.state.engine = engine

    @app.middleware("http")
    async def limit_body(request: Request, call_next):
        if int(request.headers.get("content-length", "0") or 0) > MAX_BODY_BYTES:
            from fastapi.responses import JSONResponse
            return JSONResponse({"detail": "Payload terlalu besar"}, status_code=413)
        return await call_next(request)

    def authenticate(payload: IngestRequest, x_sensor_key: str = Header(default="")) -> dict:
        sensor = repo.get_sensor(payload.sensor_code)
        supplied = hashlib.sha256(x_sensor_key.encode()).hexdigest()
        stored = (sensor or {}).get("api_key_hash", "0" * 64)
        if not sensor or not hmac.compare_digest(supplied, stored):
            raise HTTPException(status_code=401, detail="Sensor atau kunci tidak valid")
        return sensor

    @app.get("/health")
    def health():
        return {"status": "ok"}

    @app.post("/api/v1/ingest")
    def ingest(payload: IngestRequest, sensor: dict = Depends(authenticate)):
        sensor = {**sensor, "hostname": payload.hostname or sensor.get("hostname"), "os": payload.os or sensor.get("os")}
        events = [e.model_dump(exclude_none=True) for e in payload.events]
        return {"status": "accepted", **engine.process_batch(sensor, events)}

    return app


def email_pending(repo: Repository) -> None:
    if not smtp_enabled():
        return
    to = str(repo.load_settings().get("alert_email", "")).strip()
    if not to:
        return
    for a in repo.unemailed_alerts():
        if send_alert_email(to, f"[{a['priority'].upper()}] Peringatan keamanan", a["message"]):
            repo.mark_emailed(a["id"])


def get_app() -> FastAPI:
    """Factory untuk uvicorn: uvicorn app.main:get_app --factory"""
    return create_app()
