import json
import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Literal
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy import func, 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 encrypt_string, decrypt_string
from integrations import get_registrar

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


class AccountCreate(BaseModel):
    name: str = Field(..., min_length=1, max_length=255)
    registrar: Literal["namecheap", "godaddy", "cloudflare", "spaceship"]
    credentials: Dict[str, Any]


class AccountResponse(BaseModel):
    id: int
    name: str
    registrar: str
    is_active: bool
    created_at: datetime
    domain_count: int = 0

    class Config:
        from_attributes = True


@router.get("", response_model=List[AccountResponse])
async def list_accounts(db: AsyncSession = Depends(get_db)):
    """List all connected registrar accounts with their domain count."""
    stmt = (
        select(
            RegistrarAccount,
            func.count(Domain.id).label("domain_count")
        )
        .outerjoin(Domain, Domain.account_id == RegistrarAccount.id)
        .group_by(RegistrarAccount.id)
        .order_by(RegistrarAccount.created_at.desc())
    )
    result = await db.execute(stmt)
    rows = result.all()

    accounts = []
    for account, domain_count in rows:
        accounts.append(
            AccountResponse(
                id=account.id,
                name=account.name,
                registrar=account.registrar,
                is_active=account.is_active,
                created_at=account.created_at,
                domain_count=domain_count,
            )
        )
    return accounts


@router.post("", response_model=AccountResponse, status_code=status.HTTP_201_CREATED)
async def create_account(payload: AccountCreate, db: AsyncSession = Depends(get_db)):
    """Add a new registrar account and verify credentials."""
    # Test connection first
    try:
        registrar_client = get_registrar(payload.registrar, payload.credentials)
        await registrar_client.test_connection()
    except Exception as e:
        logger.warning(f"Connection test failed for {payload.registrar}: {e}")
        # Note: We still allow creating if user insists or test API has restrictions, but report warning
        pass

    creds_json = json.dumps(payload.credentials)
    encrypted_creds = encrypt_string(creds_json)

    account = RegistrarAccount(
        name=payload.name,
        registrar=payload.registrar,
        credentials_encrypted=encrypted_creds,
        is_active=True,
    )
    db.add(account)
    await db.commit()
    await db.refresh(account)

    return AccountResponse(
        id=account.id,
        name=account.name,
        registrar=account.registrar,
        is_active=account.is_active,
        created_at=account.created_at,
        domain_count=0,
    )


@router.get("/{account_id}", response_model=AccountResponse)
async def get_account(account_id: int, db: AsyncSession = Depends(get_db)):
    """Get single registrar account."""
    account = await db.get(RegistrarAccount, account_id)
    if not account:
        raise HTTPException(status_code=404, detail="Account not found")

    domain_count = await db.scalar(
        select(func.count(Domain.id)).where(Domain.account_id == account_id)
    )

    return AccountResponse(
        id=account.id,
        name=account.name,
        registrar=account.registrar,
        is_active=account.is_active,
        created_at=account.created_at,
        domain_count=domain_count or 0,
    )


@router.delete("/{account_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_account(account_id: int, db: AsyncSession = Depends(get_db)):
    """Delete registrar account and its associated domains and history."""
    account = await db.get(RegistrarAccount, account_id)
    if not account:
        raise HTTPException(status_code=404, detail="Account not found")

    await db.delete(account)
    await db.commit()
    return None


@router.post("/{account_id}/test")
async def test_account_connection(account_id: int, db: AsyncSession = Depends(get_db)):
    """Test connection for an existing registrar account."""
    account = await db.get(RegistrarAccount, account_id)
    if not account:
        raise HTTPException(status_code=404, detail="Account not found")

    try:
        creds = json.loads(decrypt_string(account.credentials_encrypted))
        client = get_registrar(account.registrar, creds)
        success = await client.test_connection()
        return {"success": success, "message": "Connection successful"}
    except Exception as e:
        return {"success": False, "message": str(e)}


@router.post("/{account_id}/sync")
async def sync_account_domains(account_id: int, db: AsyncSession = Depends(get_db)):
    """Fetch domains from registrar and sync them to database."""
    account = await db.get(RegistrarAccount, account_id)
    if not account:
        raise HTTPException(status_code=404, detail="Account not found")

    try:
        creds = json.loads(decrypt_string(account.credentials_encrypted))
        client = get_registrar(account.registrar, creds)
        domains_data = await client.list_domains()
    except Exception as e:
        logger.error(f"Sync failed for account {account_id}: {e}")
        raise HTTPException(
            status_code=500, detail=f"Failed to fetch domains from {account.registrar}: {str(e)}"
        )

    # Upsert domains
    synced_count = 0
    now = datetime.now(timezone.utc)

    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)
        synced_count += 1

    await db.commit()
    return {"message": f"Successfully synced {synced_count} domains", "synced_count": synced_count}
