"""Settings router — cemetery profile and fee catalog."""
import logging
from uuid import UUID
from typing import Optional

import arq
from arq.connections import RedisSettings
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.config import settings
from src.core.dependencies import require_min_role, require_tenant
from src.core.constants import UserRole
from src.core.schemas.response import success, paginated
from src.core.exceptions import NotFoundError
from src.database.session import get_db
from src.apps.auth.models.user import User
from src.apps.tenants.models.account import Account
from src.apps.settings.models.fee_item import FeeItem
from src.apps.plots.models.plot_type import PlotType
from src.apps.settings.models.email_template import EmailTemplate
from src.apps.settings.schemas.requests import (
    FeeItemCreateRequest,
    FeeItemUpdateRequest,
    CemeteryProfileUpdateRequest,
    GenerateQRRequest,
    RegenerateAllRequest,
)
from src.apps.settings.schemas.responses import (
    FeeItemResponse,
    CemeteryProfileResponse,
    QRCodeResponse,
)
from src.apps.settings.services.qr_code_service import QRCodeService

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/settings", tags=["settings"])


def _serialize_plot_type(pt) -> dict:
    w = float(pt.default_width_m) if pt.default_width_m else None
    l_ = float(pt.default_length_m) if pt.default_length_m else None
    dims = f"{w} m × {l_} m" if w and l_ else None
    return {
        "id": str(pt.id),
        "name": pt.name,
        "dimensions": dims,
        "default_width_m": w,
        "default_length_m": l_,
        "default_depth_m": float(pt.default_depth_m) if pt.default_depth_m else None,
        "capacity": pt.capacity,
        "default_price": float(pt.default_price) if pt.default_price else None,
        "sections": pt.sections,
        "created_at": pt.created_at.isoformat(),
        "updated_at": pt.updated_at.isoformat(),
    }


def _serialize_template(t) -> dict:
    return {
        "id": str(t.id),
        "name": t.name,
        "trigger": t.trigger,
        "subject": t.subject,
        "body": t.body,
        "enabled": t.is_enabled,
        "is_enabled": t.is_enabled,
        "variables": t.variables or [],
        "created_at": t.created_at.isoformat(),
        "updated_at": t.updated_at.isoformat(),
    }


# --- Cemetery profile ---

@router.get("/profile")
async def get_profile(
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(Account).where(Account.id == tenant_id))
    account = result.scalar_one_or_none()
    if not account:
        raise NotFoundError("Cemetery profile not found")
    return success(CemeteryProfileResponse.model_validate(account))


@router.patch("/profile")
async def update_profile(
    body: CemeteryProfileUpdateRequest,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(select(Account).where(Account.id == tenant_id))
    account = result.scalar_one_or_none()
    if not account:
        raise NotFoundError("Cemetery profile not found")
    for field, value in body.model_dump(exclude_unset=True).items():
        setattr(account, field, value)
    await db.flush()
    await db.refresh(account)
    return success(CemeteryProfileResponse.model_validate(account))


# --- Fee catalog ---

@router.get("/fees")
async def list_fees(
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=200),
    category: Optional[str] = Query(None),
    active_only: bool = Query(True),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    filters = [FeeItem.tenant_id == tenant_id]
    if category:
        filters.append(FeeItem.category == category)
    if active_only:
        filters.append(FeeItem.is_active.is_(True))

    total = (
        await db.execute(select(func.count()).select_from(FeeItem).where(*filters))
    ).scalar_one()
    offset = (page - 1) * page_size
    result = await db.execute(
        select(FeeItem).where(*filters)
        .order_by(FeeItem.sort_order, FeeItem.name)
        .offset(offset).limit(page_size)
    )
    items = [FeeItemResponse.model_validate(f) for f in result.scalars().all()]
    return paginated(items, total, page, page_size)


@router.post("/fees", status_code=201)
async def create_fee(
    body: FeeItemCreateRequest,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    fee = FeeItem(tenant_id=tenant_id, **body.model_dump())
    db.add(fee)
    await db.flush()
    await db.refresh(fee)
    return success(FeeItemResponse.model_validate(fee), "Fee item created")


@router.patch("/fees/{fee_id}")
async def update_fee(
    fee_id: UUID,
    body: FeeItemUpdateRequest,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(FeeItem).where(FeeItem.id == fee_id, FeeItem.tenant_id == tenant_id)
    )
    fee = result.scalar_one_or_none()
    if not fee:
        raise NotFoundError("Fee item not found")
    for field, value in body.model_dump(exclude_unset=True).items():
        setattr(fee, field, value)
    await db.flush()
    await db.refresh(fee)
    return success(FeeItemResponse.model_validate(fee))


@router.delete("/fees/{fee_id}", status_code=204)
async def delete_fee(
    fee_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(FeeItem).where(FeeItem.id == fee_id, FeeItem.tenant_id == tenant_id)
    )
    fee = result.scalar_one_or_none()
    if not fee:
        raise NotFoundError("Fee item not found")
    await db.delete(fee)


# --- QR Codes ---

@router.get("/qr-codes")
async def list_qr_codes(
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    qr_type: Optional[str] = Query(None),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    items, total = await QRCodeService.get_list(
        db, tenant_id, qr_type=qr_type, page=page, page_size=page_size
    )
    data = [QRCodeResponse.model_validate(q) for q in items]
    return paginated(data, total, page, page_size)


@router.post("/qr-codes/generate", status_code=202)
async def generate_qr_code_endpoint(
    body: GenerateQRRequest,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    arq_redis = None
    try:
        arq_redis = await arq.create_pool(RedisSettings.from_dsn(settings.REDIS_URL))
    except Exception as exc:
        logger.warning(f"[qr] Redis unavailable: {exc}")
    try:
        result = await QRCodeService.generate_one(
            db,
            tenant_id,
            qr_type=body.qr_type,
            reference_id=body.reference_id,
            display_label=body.display_label,
            arq_redis=arq_redis,
        )
    finally:
        if arq_redis is not None:
            await arq_redis.aclose()
    return success(result, "QR code generation enqueued")


@router.post("/qr-codes/regenerate-all", status_code=202)
async def regenerate_all_qr_codes(
    body: RegenerateAllRequest,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    arq_redis = None
    try:
        arq_redis = await arq.create_pool(RedisSettings.from_dsn(settings.REDIS_URL))
    except Exception as exc:
        logger.warning(f"[qr] Redis unavailable: {exc}")
    try:
        result = await QRCodeService.regenerate_all(
            db,
            tenant_id,
            qr_type=body.qr_type,
            arq_redis=arq_redis,
        )
    finally:
        if arq_redis is not None:
            await arq_redis.aclose()
    return success(result, f"Enqueued {result['enqueued']} QR generation jobs")


@router.get("/qr-codes/{qr_id}/download")
async def download_qr_code(
    qr_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    url = await QRCodeService.get_download_url(db, tenant_id, qr_id)
    return success({"download_url": url})


# ── Plot Types ────────────────────────────────────────────────────────────────

@router.get("/plot-types", response_model=dict)
async def list_plot_types(
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=200),
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    offset = (page - 1) * page_size
    count_result = await db.execute(
        select(func.count(PlotType.id)).where(PlotType.tenant_id == tenant_id)
    )
    total = count_result.scalar_one()
    result = await db.execute(
        select(PlotType).where(PlotType.tenant_id == tenant_id)
        .order_by(PlotType.name.asc()).offset(offset).limit(page_size)
    )
    items = result.scalars().all()
    return paginated(
        items=[_serialize_plot_type(pt) for pt in items],
        total=total, page=page, page_size=page_size,
    )


@router.post("/plot-types", response_model=dict, status_code=201)
async def create_plot_type(
    body: dict,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    pt = PlotType(
        tenant_id=tenant_id,
        name=body.get("name"),
        default_width_m=body.get("default_width_m"),
        default_length_m=body.get("default_length_m"),
        default_depth_m=body.get("default_depth_m"),
        capacity=body.get("capacity", 1),
        default_price=body.get("default_price"),
        sections=body.get("sections"),
    )
    db.add(pt)
    await db.flush()
    await db.refresh(pt)
    return success(data=_serialize_plot_type(pt))


@router.patch("/plot-types/{plot_type_id}", response_model=dict)
async def update_plot_type(
    plot_type_id: UUID,
    body: dict,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(PlotType).where(PlotType.id == plot_type_id, PlotType.tenant_id == tenant_id)
    )
    pt = result.scalar_one_or_none()
    if not pt:
        raise NotFoundError("Plot type not found")
    allowed = ["name", "default_width_m", "default_length_m", "default_depth_m", "capacity", "default_price", "sections"]
    for field in allowed:
        if field in body:
            setattr(pt, field, body[field])
    await db.flush()
    await db.refresh(pt)
    return success(data=_serialize_plot_type(pt))


@router.delete("/plot-types/{plot_type_id}", status_code=204)
async def delete_plot_type(
    plot_type_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(PlotType).where(PlotType.id == plot_type_id, PlotType.tenant_id == tenant_id)
    )
    pt = result.scalar_one_or_none()
    if not pt:
        raise NotFoundError("Plot type not found")
    await db.delete(pt)


# ── Email Templates ───────────────────────────────────────────────────────────

@router.get("/email-templates", response_model=dict)
async def list_email_templates(
    current_user: User = Depends(require_min_role(UserRole.STAFF)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(EmailTemplate).where(EmailTemplate.tenant_id == tenant_id)
        .order_by(EmailTemplate.name.asc())
    )
    items = result.scalars().all()
    return success(data={"items": [_serialize_template(t) for t in items], "total": len(items)})


@router.post("/email-templates", response_model=dict, status_code=201)
async def create_email_template(
    body: dict,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    t = EmailTemplate(
        tenant_id=tenant_id,
        name=body.get("name", ""),
        trigger=body.get("trigger"),
        subject=body.get("subject", ""),
        body=body.get("body", ""),
        is_enabled=body.get("is_enabled", True),
        variables=body.get("variables"),
    )
    db.add(t)
    await db.flush()
    await db.refresh(t)
    return success(data=_serialize_template(t))


@router.patch("/email-templates/{template_id}", response_model=dict)
async def update_email_template(
    template_id: UUID,
    body: dict,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(EmailTemplate).where(EmailTemplate.id == template_id, EmailTemplate.tenant_id == tenant_id)
    )
    t = result.scalar_one_or_none()
    if not t:
        raise NotFoundError("Template not found")
    for field in ["name", "trigger", "subject", "body", "is_enabled", "variables"]:
        if field in body:
            setattr(t, field, body[field])
    await db.flush()
    await db.refresh(t)
    return success(data=_serialize_template(t))


@router.delete("/email-templates/{template_id}", status_code=204)
async def delete_email_template(
    template_id: UUID,
    current_user: User = Depends(require_min_role(UserRole.ADMINISTRATOR)),
    tenant_id: str = Depends(require_tenant),
    db: AsyncSession = Depends(get_db),
):
    result = await db.execute(
        select(EmailTemplate).where(EmailTemplate.id == template_id, EmailTemplate.tenant_id == tenant_id)
    )
    t = result.scalar_one_or_none()
    if not t:
        raise NotFoundError("Template not found")
    await db.delete(t)
