import calendar
from datetime import date, datetime, timezone
import json
import logging
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import desc, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from database import get_db
from models import Domain, RegistrarAccount
from crypto import decrypt_string
from integrations import get_registrar

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/domains", tags=["domains"])


class DomainResponse(BaseModel):
    id: int
    domain_name: str
    account_id: int
    account_name: str
    registrar: str
    expiry_date: Optional[date] = None
    days_until_expiry: Optional[int] = None
    auto_renew: bool
    status: str
    last_synced: Optional[datetime] = None

    class Config:
        from_attributes = True


def calculate_days_until_expiry(expiry_date: Optional[date]) -> Optional[int]:
    if not expiry_date:
        return None
    today = date.today()
    return (expiry_date - today).days


@router.get("", response_model=Dict[str, Any])
async def list_domains(
    search: Optional[str] = None,
    registrar: Optional[str] = None,
    status_filter: Optional[str] = Query(None, alias="status"),
    sort_by: str = Query("expiry_date", enum=["expiry_date", "domain_name", "last_synced"]),
    order: str = Query("asc", enum=["asc", "desc"]),
    page: int = Query(1, ge=1),
    limit: int = Query(50, ge=1, le=200),
    db: AsyncSession = Depends(get_db),
):
    """List domains with search, filter, and pagination."""
    query = (
        select(Domain, RegistrarAccount)
        .join(RegistrarAccount, Domain.account_id == RegistrarAccount.id)
    )

    if search:
        query = query.where(Domain.domain_name.ilike(f"%{search.strip()}%"))
    if registrar:
        query = query.where(RegistrarAccount.registrar == registrar.lower())
    if status_filter:
        query = query.where(Domain.status == status_filter.lower())

    # Count total
    count_query = select(func.count()).select_from(query.subquery())
    total = (await db.execute(count_query)).scalar_one()

    # Sorting
    sort_col = getattr(Domain, sort_by, Domain.expiry_date)
    if sort_by == "expiry_date":
        # Put NULL expiry dates at the end
        if order == "asc":
            query = query.order_by(Domain.expiry_date.asc().nullslast())
        else:
            query = query.order_by(Domain.expiry_date.desc().nullslast())
    else:
        if order == "desc":
            query = query.order_by(desc(sort_col))
        else:
            query = query.order_by(sort_col)

    # Pagination
    offset = (page - 1) * limit
    query = query.offset(offset).limit(limit)

    results = await db.execute(query)
    rows = results.all()

    domains = []
    for domain, account in rows:
        domains.append(
            DomainResponse(
                id=domain.id,
                domain_name=domain.domain_name,
                account_id=account.id,
                account_name=account.name,
                registrar=account.registrar,
                expiry_date=domain.expiry_date,
                days_until_expiry=calculate_days_until_expiry(domain.expiry_date),
                auto_renew=domain.auto_renew,
                status=domain.status,
                last_synced=domain.last_synced,
            )
        )

    return {
        "items": domains,
        "total": total,
        "page": page,
        "limit": limit,
        "pages": (total + limit - 1) // limit if limit else 1,
    }


@router.get("/expiring", response_model=List[DomainResponse])
async def list_expiring_this_month(db: AsyncSession = Depends(get_db)):
    """
    Get all domains expiring in the current calendar month across all registrar accounts.
    """
    today = date.today()
    _, last_day = calendar.monthrange(today.year, today.month)
    month_end = date(today.year, today.month, last_day)

    query = (
        select(Domain, RegistrarAccount)
        .join(RegistrarAccount, Domain.account_id == RegistrarAccount.id)
        .where(
            Domain.expiry_date.isnot(None),
            Domain.expiry_date >= today,
            Domain.expiry_date <= month_end,
        )
        .order_by(Domain.expiry_date.asc())
    )

    results = await db.execute(query)
    rows = results.all()

    domains = []
    for domain, account in rows:
        domains.append(
            DomainResponse(
                id=domain.id,
                domain_name=domain.domain_name,
                account_id=account.id,
                account_name=account.name,
                registrar=account.registrar,
                expiry_date=domain.expiry_date,
                days_until_expiry=calculate_days_until_expiry(domain.expiry_date),
                auto_renew=domain.auto_renew,
                status=domain.status,
                last_synced=domain.last_synced,
            )
        )
    return domains


@router.get("/expiring/soon", response_model=List[DomainResponse])
async def list_expiring_soon(
    days: int = Query(30, ge=1, le=365),
    db: AsyncSession = Depends(get_db),
):
    """Get all domains expiring within the next N days."""
    today = date.today()
    target_date = today + calendar.datetime.timedelta(days=days)

    query = (
        select(Domain, RegistrarAccount)
        .join(RegistrarAccount, Domain.account_id == RegistrarAccount.id)
        .where(
            Domain.expiry_date.isnot(None),
            Domain.expiry_date >= today,
            Domain.expiry_date <= target_date,
        )
        .order_by(Domain.expiry_date.asc())
    )

    results = await db.execute(query)
    rows = results.all()

    domains = []
    for domain, account in rows:
        domains.append(
            DomainResponse(
                id=domain.id,
                domain_name=domain.domain_name,
                account_id=account.id,
                account_name=account.name,
                registrar=account.registrar,
                expiry_date=domain.expiry_date,
                days_until_expiry=calculate_days_until_expiry(domain.expiry_date),
                auto_renew=domain.auto_renew,
                status=domain.status,
                last_synced=domain.last_synced,
            )
        )
    return domains


@router.get("/{domain_id}", response_model=DomainResponse)
async def get_domain(domain_id: int, db: AsyncSession = Depends(get_db)):
    """Get single domain details."""
    query = (
        select(Domain, RegistrarAccount)
        .join(RegistrarAccount, Domain.account_id == RegistrarAccount.id)
        .where(Domain.id == domain_id)
    )
    result = (await db.execute(query)).first()
    if not result:
        raise HTTPException(status_code=404, detail="Domain not found")

    domain, account = result
    return DomainResponse(
        id=domain.id,
        domain_name=domain.domain_name,
        account_id=account.id,
        account_name=account.name,
        registrar=account.registrar,
        expiry_date=domain.expiry_date,
        days_until_expiry=calculate_days_until_expiry(domain.expiry_date),
        auto_renew=domain.auto_renew,
        status=domain.status,
        last_synced=domain.last_synced,
    )


@router.post("/sync-all")
async def sync_all_accounts(db: AsyncSession = Depends(get_db)):
    """Sync all active registrar accounts."""
    accounts = (await db.execute(select(RegistrarAccount).where(RegistrarAccount.is_active == True))).scalars().all()
    
    total_synced = 0
    errors = []
    now = datetime.now(timezone.utc)

    for account in accounts:
        try:
            creds = json.loads(decrypt_string(account.credentials_encrypted))
            client = get_registrar(account.registrar, creds)
            domains_data = await client.list_domains()

            for item in domains_data:
                domain_name = item.get("domain_name", "").lower().strip()
                if not domain_name:
                    continue

                stmt = select(Domain).where(
                    Domain.account_id == account.id,
                    Domain.domain_name == domain_name,
                )
                existing = (await db.execute(stmt)).scalar_one_or_none()

                if existing:
                    if item.get("expiry_date"):
                        existing.expiry_date = item["expiry_date"]
                    if "auto_renew" in item:
                        existing.auto_renew = item["auto_renew"]
                    if item.get("status"):
                        existing.status = item["status"]
                    if item.get("registrar_domain_id"):
                        existing.registrar_domain_id = item["registrar_domain_id"]
                    existing.last_synced = now
                else:
                    new_domain = Domain(
                        account_id=account.id,
                        domain_name=domain_name,
                        expiry_date=item.get("expiry_date"),
                        auto_renew=item.get("auto_renew", False),
                        status=item.get("status", "active"),
                        registrar_domain_id=item.get("registrar_domain_id"),
                        last_synced=now,
                    )
                    db.add(new_domain)
                total_synced += 1
        except Exception as e:
            logger.error(f"Error syncing account {account.id} ({account.name}): {e}")
            errors.append(f"{account.name}: {str(e)}")

    await db.commit()
    return {
        "message": f"Synced {total_synced} domains across {len(accounts)} accounts",
        "total_synced": total_synced,
        "errors": errors,
    }
