"""API + panel routes for remote IMAP mailbox import."""

from __future__ import annotations

import logging
from typing import List, Optional

from fastapi import APIRouter, Depends, Form, HTTPException, Query, Request
from fastapi.responses import JSONResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, ConfigDict, Field, field_validator
from sqlalchemy.orm import Session, joinedload

from .. import models
from ..admin_ops import panel_template_dir
from ..db import get_db
from ..mailbox_import import (
    AUTH_MODES,
    PROTOCOLS,
    SourceConfig,
    create_job,
    default_port_for,
    delete_job,
    ensure_schema,
    ensure_worker_started,
    get_job,
    list_jobs,
    normalize_auth_mode,
    normalize_protocol,
    request_cancel,
    serialize_job,
    test_connection,
)
from ..security import require_admin, require_panel_csrf, require_panel_session
from ..settings import get_settings

logger = logging.getLogger(__name__)
router = APIRouter(tags=["mailbox-import"])
settings = get_settings()
templates = Jinja2Templates(directory=str(panel_template_dir()))


class MailboxImportCreate(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)

    destination_account_id: int = Field(..., ge=1)
    source_host: str = Field(..., min_length=1, max_length=255)
    source_port: Optional[int] = Field(default=None, ge=1, le=65535)
    source_protocol: str = Field(default="imaps")
    source_username: str = Field(..., min_length=1, max_length=320)
    source_password: str = Field(..., min_length=1, max_length=512)
    source_auth_mode: str = Field(default="login")

    @field_validator("source_protocol")
    @classmethod
    def validate_protocol(cls, value: str) -> str:
        return normalize_protocol(value)

    @field_validator("source_auth_mode")
    @classmethod
    def validate_auth(cls, value: str) -> str:
        return normalize_auth_mode(value)

    @field_validator("source_host")
    @classmethod
    def validate_host(cls, value: str) -> str:
        host = value.strip()
        if not host or any(c in host for c in " \t\r\n"):
            raise ValueError("Invalid source host")
        return host


class MailboxImportTest(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)

    source_host: str = Field(..., min_length=1, max_length=255)
    source_port: Optional[int] = Field(default=None, ge=1, le=65535)
    source_protocol: str = Field(default="imaps")
    source_username: str = Field(..., min_length=1, max_length=320)
    source_password: str = Field(..., min_length=1, max_length=512)
    source_auth_mode: str = Field(default="login")

    @field_validator("source_protocol")
    @classmethod
    def validate_protocol(cls, value: str) -> str:
        return normalize_protocol(value)

    @field_validator("source_auth_mode")
    @classmethod
    def validate_auth(cls, value: str) -> str:
        return normalize_auth_mode(value)


def _get_account(db: Session, account_id: int) -> models.Account:
    account = (
        db.query(models.Account)
        .options(joinedload(models.Account.domain))
        .filter(models.Account.id == account_id)
        .first()
    )
    if not account:
        raise HTTPException(status_code=404, detail="Destination account not found")
    if not account.is_active:
        raise HTTPException(status_code=400, detail="Destination account is suspended")
    if not account.domain or not account.domain.is_active:
        raise HTTPException(status_code=400, detail="Destination domain is inactive")
    return account


# ---------------------------------------------------------------------------
# REST API
# ---------------------------------------------------------------------------


@router.post("/mailbox-import/test", response_model=dict)
def api_test_connection(payload: MailboxImportTest, _: str = Depends(require_admin)):
    port = payload.source_port or default_port_for(payload.source_protocol)
    cfg = SourceConfig(
        host=payload.source_host,
        port=port,
        protocol=payload.source_protocol,
        username=payload.source_username,
        password=payload.source_password,
        auth_mode=payload.source_auth_mode,
    )
    try:
        return test_connection(cfg)
    except Exception as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.post("/mailbox-import", response_model=dict, status_code=201)
def api_create_import(payload: MailboxImportCreate, db: Session = Depends(get_db), _: str = Depends(require_admin)):
    ensure_schema(db)
    account = _get_account(db, payload.destination_account_id)
    try:
        job = create_job(
            db,
            account=account,
            host=payload.source_host,
            port=payload.source_port,
            protocol=payload.source_protocol,
            username=payload.source_username,
            password=payload.source_password,
            auth_mode=payload.source_auth_mode,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    return serialize_job(job)


@router.get("/mailbox-import", response_model=List[dict])
def api_list_imports(
    limit: int = Query(default=50, ge=1, le=500),
    status: Optional[str] = Query(default=None),
    db: Session = Depends(get_db),
    _: str = Depends(require_admin),
):
    ensure_schema(db)
    ensure_worker_started()
    jobs = list_jobs(db, limit=limit, status=status)
    return [serialize_job(job) for job in jobs]


@router.get("/mailbox-import/{job_id}", response_model=dict)
def api_get_import(job_id: int, db: Session = Depends(get_db), _: str = Depends(require_admin)):
    ensure_schema(db)
    job = get_job(db, job_id)
    if not job:
        raise HTTPException(status_code=404, detail="Import job not found")
    return serialize_job(job)


@router.post("/mailbox-import/{job_id}/cancel", response_model=dict)
def api_cancel_import(job_id: int, db: Session = Depends(get_db), _: str = Depends(require_admin)):
    job = get_job(db, job_id)
    if not job:
        raise HTTPException(status_code=404, detail="Import job not found")
    job = request_cancel(db, job)
    return serialize_job(job)


@router.delete("/mailbox-import/{job_id}", response_model=dict)
def api_delete_import(job_id: int, db: Session = Depends(get_db), _: str = Depends(require_admin)):
    job = get_job(db, job_id)
    if not job:
        raise HTTPException(status_code=404, detail="Import job not found")
    try:
        delete_job(db, job)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    return {"status": "deleted", "id": job_id}


# ---------------------------------------------------------------------------
# Panel
# ---------------------------------------------------------------------------


def _panel_accounts(db: Session) -> list[dict]:
    rows = (
        db.query(models.Account)
        .options(joinedload(models.Account.domain))
        .order_by(models.Account.username.asc())
        .all()
    )
    return [
        {
            "id": row.id,
            "username": row.username,
            "is_active": bool(row.is_active),
            "domain": row.domain.name if row.domain else "",
        }
        for row in rows
        if row.domain
    ]


def _import_panel_context(request: Request, session: dict, db: Session, **extra):
    from .admin import panel_context

    ensure_schema(db)
    ensure_worker_started()
    jobs = [serialize_job(job) for job in list_jobs(db, limit=100)]
    accounts = _panel_accounts(db)
    active_tab = extra.pop("import_tab", None) or request.query_params.get("tab") or "new"
    if active_tab not in {"new", "jobs"}:
        active_tab = "new"
    ctx = panel_context(
        request,
        session,
        "import",
        import_jobs=jobs,
        import_accounts=accounts,
        active_import_tab=active_tab,
        import_protocols=sorted(PROTOCOLS),
        import_auth_modes=sorted(AUTH_MODES),
        default_import_port=993,
        **extra,
    )
    return ctx


@router.get("/panel/import")
def panel_import(
    request: Request,
    notice: Optional[str] = None,
    error: Optional[str] = None,
    tab: Optional[str] = None,
    session: dict = Depends(require_panel_session),
    db: Session = Depends(get_db),
):
    from .admin import ensure_web_panel_enabled

    ensure_web_panel_enabled()
    notices = {
        "import-queued": "Import job queued. Progress is updated on the Jobs tab.",
        "import-cancelled": "Cancel requested for the import job.",
        "import-deleted": "Import job removed.",
        "connection-ok": "Source IMAP connection successful.",
    }
    context = _import_panel_context(
        request,
        session,
        db,
        notice=notices.get(notice, notice),
        error=error,
        import_tab=tab,
    )
    return templates.TemplateResponse(request, "panel.html", context)


@router.get("/panel/import/jobs.json")
def panel_import_jobs_json(
    session: dict = Depends(require_panel_session),
    db: Session = Depends(get_db),
):
    from .admin import ensure_web_panel_enabled

    ensure_web_panel_enabled()
    ensure_schema(db)
    ensure_worker_started()
    jobs = [serialize_job(job) for job in list_jobs(db, limit=100)]
    return JSONResponse({"jobs": jobs})


@router.post("/panel/import")
def panel_import_create(
    request: Request,
    csrf_token: str = Form(...),
    destination_account_id: int = Form(...),
    source_host: str = Form(...),
    source_port: str = Form(""),
    source_protocol: str = Form("imaps"),
    source_username: str = Form(...),
    source_password: str = Form(...),
    source_auth_mode: str = Form("login"),
    action: str = Form("import"),
    session: dict = Depends(require_panel_session),
    db: Session = Depends(get_db),
):
    from .admin import ensure_web_panel_enabled, panel_redirect

    ensure_web_panel_enabled()
    require_panel_csrf(session, csrf_token)

    def fail(message: str, tab: str = "new"):
        return panel_redirect("/panel/import", error=message, tab=tab)

    try:
        protocol = normalize_protocol(source_protocol)
        auth_mode = normalize_auth_mode(source_auth_mode)
    except ValueError as exc:
        return fail(str(exc))

    port_raw = (source_port or "").strip()
    port = int(port_raw) if port_raw else default_port_for(protocol)
    if port < 1 or port > 65535:
        return fail("Port must be between 1 and 65535")

    if action == "test":
        cfg = SourceConfig(
            host=source_host.strip(),
            port=port,
            protocol=protocol,
            username=source_username.strip(),
            password=source_password,
            auth_mode=auth_mode,
        )
        try:
            result = test_connection(cfg)
        except Exception as exc:
            return fail(str(exc))
        folder_count = result.get("folder_count", 0)
        return panel_redirect(
            "/panel/import",
            notice=f"connection-ok — {folder_count} folders visible on source",
            tab="new",
        )

    account = (
        db.query(models.Account)
        .options(joinedload(models.Account.domain))
        .filter(models.Account.id == destination_account_id)
        .first()
    )
    if not account:
        return fail("Destination account not found")
    if not account.is_active:
        return fail("Destination account is suspended")
    try:
        create_job(
            db,
            account=account,
            host=source_host,
            port=port,
            protocol=protocol,
            username=source_username,
            password=source_password,
            auth_mode=auth_mode,
        )
    except ValueError as exc:
        return fail(str(exc))
    return panel_redirect("/panel/import", notice="import-queued", tab="jobs")


@router.post("/panel/import/{job_id}/cancel")
def panel_import_cancel(
    job_id: int,
    csrf_token: str = Form(...),
    session: dict = Depends(require_panel_session),
    db: Session = Depends(get_db),
):
    from .admin import ensure_web_panel_enabled, panel_redirect

    ensure_web_panel_enabled()
    require_panel_csrf(session, csrf_token)
    job = get_job(db, job_id)
    if not job:
        return panel_redirect("/panel/import", error="Import job not found", tab="jobs")
    request_cancel(db, job)
    return panel_redirect("/panel/import", notice="import-cancelled", tab="jobs")


@router.post("/panel/import/{job_id}/delete")
def panel_import_delete(
    job_id: int,
    csrf_token: str = Form(...),
    session: dict = Depends(require_panel_session),
    db: Session = Depends(get_db),
):
    from .admin import ensure_web_panel_enabled, panel_redirect

    ensure_web_panel_enabled()
    require_panel_csrf(session, csrf_token)
    job = get_job(db, job_id)
    if not job:
        return panel_redirect("/panel/import", error="Import job not found", tab="jobs")
    try:
        delete_job(db, job)
    except ValueError as exc:
        return panel_redirect("/panel/import", error=str(exc), tab="jobs")
    return panel_redirect("/panel/import", notice="import-deleted", tab="jobs")
