from typing import List

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session

from .. import models, schemas
from ..db import get_db
from ..cache import CACHE_TTL_SECONDS, get_cache, safe_cache_delete, safe_cache_set
from ..security import require_admin

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


def get_domain_by_name(db: Session, name: str) -> models.Domain:
    domain = db.query(models.Domain).filter(models.Domain.name == name).first()
    if not domain:
        raise HTTPException(status_code=404, detail="Domain not found")
    if not domain.is_active:
        raise HTTPException(status_code=400, detail="Domain is inactive")
    return domain


def cache_redirect(cache, source: str, redirect: models.Redirect) -> None:
    payload = f"{redirect.target_email}|keep_copy={int(bool(redirect.keep_copy))}"
    safe_cache_set(cache, f"redirect:{source}", payload, ex=CACHE_TTL_SECONDS)


def _require_mailbox_for_keep_copy(db: Session, domain: models.Domain, source_local: str, keep_copy: bool) -> None:
    if not keep_copy:
        return
    account = (
        db.query(models.Account)
        .filter(
            models.Account.domain_id == domain.id,
            models.Account.local_part == source_local,
            models.Account.is_active.is_(True),
        )
        .first()
    )
    if not account:
        raise HTTPException(
            status_code=400,
            detail="keep_copy requires an active mailbox for the redirect source address",
        )


@router.get("/", response_model=List[schemas.RedirectOut])
def list_redirects(db: Session = Depends(get_db), _: str = Depends(require_admin)):
    return db.query(models.Redirect).all()


@router.post("/", response_model=schemas.RedirectOut)
def create_redirect(payload: schemas.RedirectCreate, db: Session = Depends(get_db), cache=Depends(get_cache), _: str = Depends(require_admin)):
    domain = get_domain_by_name(db, payload.domain)
    source_email = f"{payload.source_local}@{domain.name}"
    exists = (
        db.query(models.Redirect)
        .filter(
            models.Redirect.domain_id == domain.id,
            models.Redirect.source_local == payload.source_local,
            models.Redirect.target_email == payload.target_email,
        )
        .first()
    )
    if exists:
        raise HTTPException(status_code=400, detail="Redirect already exists")
    _require_mailbox_for_keep_copy(db, domain, payload.source_local, payload.keep_copy)
    redirect = models.Redirect(
        domain_id=domain.id,
        source_local=payload.source_local,
        target_email=payload.target_email,
        keep_copy=bool(payload.keep_copy),
        is_active=payload.is_active,
    )
    db.add(redirect)
    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="Redirect already exists") from exc
    db.refresh(redirect)
    cache_redirect(cache, source_email, redirect)
    return redirect


@router.patch("/{redirect_id}", response_model=schemas.RedirectOut)
def update_redirect(redirect_id: int, payload: schemas.RedirectUpdate, db: Session = Depends(get_db), cache=Depends(get_cache), _: str = Depends(require_admin)):
    redirect = db.query(models.Redirect).filter(models.Redirect.id == redirect_id).first()
    if not redirect:
        raise HTTPException(status_code=404, detail="Redirect not found")
    if payload.is_active is not None:
        redirect.is_active = payload.is_active
    if payload.target_email is not None:
        redirect.target_email = payload.target_email
    if payload.keep_copy is not None:
        domain = redirect.domain
        if domain is None:
            domain = db.query(models.Domain).filter(models.Domain.id == redirect.domain_id).first()
        if domain is None:
            raise HTTPException(status_code=404, detail="Domain not found")
        _require_mailbox_for_keep_copy(db, domain, redirect.source_local, payload.keep_copy)
        redirect.keep_copy = payload.keep_copy
    db.commit()
    db.refresh(redirect)
    source_email = f"{redirect.source_local}@{redirect.domain.name}" if redirect.domain else redirect.source_local
    cache_redirect(cache, source_email, redirect)
    return redirect


@router.delete("/{redirect_id}", response_model=dict)
def delete_redirect(redirect_id: int, db: Session = Depends(get_db), cache=Depends(get_cache), _: str = Depends(require_admin)):
    redirect = db.query(models.Redirect).filter(models.Redirect.id == redirect_id).first()
    if not redirect:
        raise HTTPException(status_code=404, detail="Redirect not found")
    source_email = f"{redirect.source_local}@{redirect.domain.name}" if redirect.domain else redirect.source_local
    db.delete(redirect)
    db.commit()
    safe_cache_delete(cache, f"redirect:{source_email}")
    return {"deleted": True, "source": source_email}
