import json
from typing import List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query
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
from ..utils import hash_password

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


def get_domain_by_name(db: Session, name: str, *, for_update: bool = False) -> models.Domain:
    query = db.query(models.Domain).filter(models.Domain.name == name)
    if for_update:
        query = query.with_for_update()
    domain = query.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_account(cache, email: str, account: models.Account) -> None:
    safe_cache_set(
        cache,
        f"account:{email}",
        json.dumps(
            {
                "id": account.id,
                "email": email,
                "domain_id": account.domain_id,
                "quota_mb": account.quota_mb,
                "is_active": account.is_active,
            }
        ),
        ex=CACHE_TTL_SECONDS,
    )


def serialize_account(account: models.Account) -> dict:
    domain = account.domain
    email = f"{account.local_part}@{domain.name}" if domain else account.local_part
    return {
        "id": account.id,
        "email": email,
        "domain_id": account.domain_id,
        "quota_mb": account.quota_mb,
        "is_active": account.is_active,
        "created_at": account.created_at,
    }


@router.get("/", response_model=List[schemas.AccountOut])
def list_accounts(domain: Optional[str] = Query(default=None), db: Session = Depends(get_db), _: str = Depends(require_admin)):
    query = db.query(models.Account)
    if domain:
        dom = get_domain_by_name(db, domain)
        query = query.filter(models.Account.domain_id == dom.id)
    return [serialize_account(account) for account in query.all()]


@router.get("/{account_id}", response_model=schemas.AccountOut)
def get_account(account_id: int, db: Session = Depends(get_db), _: str = Depends(require_admin)):
    account = db.query(models.Account).filter(models.Account.id == account_id).first()
    if not account:
        raise HTTPException(status_code=404, detail="Account not found")
    return serialize_account(account)


@router.post("/", response_model=schemas.AccountOut)
def create_account(payload: schemas.AccountCreate, db: Session = Depends(get_db), cache=Depends(get_cache), _: str = Depends(require_admin)):
    domain = get_domain_by_name(db, payload.domain, for_update=True)
    email = f"{payload.local_part}@{domain.name}"
    active_accounts = db.query(models.Account).filter(
        models.Account.domain_id == domain.id,
        models.Account.is_active.is_(True),
    ).count()
    if payload.is_active and domain.max_users and active_accounts >= domain.max_users:
        raise HTTPException(status_code=400, detail="Domain account limit reached")
    existing = db.query(models.Account).filter(models.Account.domain_id == domain.id, models.Account.local_part == payload.local_part).first()
    if existing:
        raise HTTPException(status_code=400, detail="Account already exists")
    account = models.Account(
        domain_id=domain.id,
        local_part=payload.local_part,
        username=email,
        password_hash=hash_password(payload.password),
        quota_mb=payload.quota_mb,
        is_active=payload.is_active,
    )
    db.add(account)
    try:
        db.commit()
    except IntegrityError as exc:
        db.rollback()
        raise HTTPException(status_code=409, detail="Account already exists") from exc
    db.refresh(account)
    cache_account(cache, email, account)
    return serialize_account(account)


@router.patch("/{account_id}", response_model=schemas.AccountOut)
def update_account(account_id: int, payload: schemas.AccountUpdate, db: Session = Depends(get_db), cache=Depends(get_cache), _: str = Depends(require_admin)):
    account = db.query(models.Account).filter(models.Account.id == account_id).with_for_update().first()
    if not account:
        raise HTTPException(status_code=404, detail="Account not found")
    domain = account.domain
    if payload.is_active is True and domain and domain.max_users:
        db.query(models.Domain).filter(models.Domain.id == domain.id).with_for_update().first()
        active_accounts = db.query(models.Account).filter(
            models.Account.domain_id == domain.id,
            models.Account.is_active.is_(True),
            models.Account.id != account.id,
        ).count()
        if active_accounts >= domain.max_users:
            raise HTTPException(status_code=400, detail="Domain account limit reached")
    if payload.password:
        account.password_hash = hash_password(payload.password)
    if payload.quota_mb is not None:
        account.quota_mb = payload.quota_mb
    if payload.is_active is not None:
        account.is_active = payload.is_active
    db.commit()
    db.refresh(account)
    email = f"{account.local_part}@{domain.name}" if domain else account.local_part
    cache_account(cache, email, account)
    return serialize_account(account)


@router.delete("/{account_id}", response_model=dict)
def delete_account(account_id: int, db: Session = Depends(get_db), cache=Depends(get_cache), _: str = Depends(require_admin)):
    account = db.query(models.Account).filter(models.Account.id == account_id).first()
    if not account:
        raise HTTPException(status_code=404, detail="Account not found")
    domain = account.domain
    email = f"{account.local_part}@{domain.name}" if domain else account.local_part
    db.delete(account)
    db.commit()
    safe_cache_delete(cache, f"account:{email}")
    return {"deleted": True, "email": email}
