"""Norms API Routes"""
import uuid
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy import select, func
from sqlalchemy.orm import selectinload
from sqlalchemy.ext.asyncio import AsyncSession
from normbase.db.connection import get_db
from normbase.models.norm import Norm, NormVersion, NormTag
from normbase.models.schemas import (
    NormCreate,
    NormUpdate,
    NormResponse,
    NormListResponse,
    NormVersionResponse,
)
from normbase.services.cache_service import CacheService
from normbase.config import settings

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


async def _invalidate_retrieve_cache() -> None:
    cache = CacheService()
    try:
        await cache.delete_by_prefix(settings.retrieve_cache_prefix)
    except Exception as e:
        print(f"Retrieve cache invalidation error: {e}")


def _build_norm_snapshot(norm: Norm) -> Dict[str, Any]:
    """Build a JSON-serializable full norm snapshot for version history."""
    return {
        "id": str(norm.id),
        "title": norm.title,
        "field_id": str(norm.field_id) if norm.field_id else None,
        "norm_type": norm.norm_type,
        "authority_level": norm.authority_level,
        "formality_level": norm.formality_level,
        "subject": norm.subject,
        "context": norm.context,
        "condition": norm.condition,
        "action": norm.action,
        "consequence": norm.consequence,
        "source": norm.source,
        "source_details": norm.source_details,
        "case_refs": norm.case_refs,
        "created_by": norm.created_by,
        "created_at": norm.created_at.isoformat() if norm.created_at else None,
        "updated_at": norm.updated_at.isoformat() if norm.updated_at else None,
        "status": norm.status,
        "version": norm.version,
        "tags": [{"tag": t.tag, "tag_category": t.tag_category} for t in norm.tags],
    }


@router.post("", response_model=NormResponse, status_code=status.HTTP_201_CREATED)
async def create_norm(
    norm_data: NormCreate,
    db: AsyncSession = Depends(get_db)
):
    """Create a new norm"""
    norm = Norm(
        title=norm_data.title,
        field_id=norm_data.field_id,
        norm_type=norm_data.norm_type,
        authority_level=norm_data.authority_level,
        formality_level=norm_data.formality_level,
        subject=norm_data.subject,
        context=norm_data.context,
        condition=norm_data.condition,
        action=norm_data.action,
        consequence=norm_data.consequence,
        source=norm_data.source,
        source_details=norm_data.source_details,
        case_refs=norm_data.case_refs,
        created_by=norm_data.created_by,
        status=norm_data.status,
    )
    norm.tags = [
        NormTag(tag=item.tag, tag_category=item.tag_category) for item in norm_data.tags
    ]
    
    db.add(norm)
    await db.commit()
    result = await db.execute(
        select(Norm).options(selectinload(Norm.tags)).where(Norm.id == norm.id)
    )
    created = result.scalar_one()
    await _invalidate_retrieve_cache()
    
    return NormResponse.model_validate(created)


@router.get("", response_model=NormListResponse)
async def list_norms(
    page: int = Query(1, ge=1, description="Page number"),
    page_size: int = Query(20, ge=1, le=100, description="Page size"),
    norm_type: Optional[str] = Query(None, description="Filter by norm type"),
    status: Optional[str] = Query(None, description="Filter by status"),
    authority_level: Optional[str] = Query(None, description="Filter by authority level"),
    formality_level: Optional[str] = Query(None, description="Filter by formality level"),
    field_id: Optional[uuid.UUID] = Query(None, description="Filter by information field"),
    db: AsyncSession = Depends(get_db)
):
    """List all norms with pagination"""
    # Build query
    query = select(Norm).options(selectinload(Norm.tags))
    count_query = select(func.count(Norm.id))
    
    # Apply filters
    if norm_type:
        query = query.where(Norm.norm_type == norm_type)
        count_query = count_query.where(Norm.norm_type == norm_type)
    
    if status:
        query = query.where(Norm.status == status)
        count_query = count_query.where(Norm.status == status)
    
    if authority_level:
        query = query.where(Norm.authority_level == authority_level)
        count_query = count_query.where(Norm.authority_level == authority_level)
    
    if formality_level:
        query = query.where(Norm.formality_level == formality_level)
        count_query = count_query.where(Norm.formality_level == formality_level)
    
    if field_id:
        query = query.where(Norm.field_id == field_id)
        count_query = count_query.where(Norm.field_id == field_id)
    
    # Get total count
    total_result = await db.execute(count_query)
    total = total_result.scalar() or 0
    
    # Get paginated results
    query = query.offset((page - 1) * page_size).limit(page_size)
    result = await db.execute(query)
    norms = result.scalars().all()
    
    return NormListResponse(
        total=total,
        items=[NormResponse.model_validate(n) for n in norms],
        page=page,
        page_size=page_size
    )


@router.get("/{norm_id}", response_model=NormResponse)
async def get_norm(
    norm_id: uuid.UUID,
    db: AsyncSession = Depends(get_db)
):
    """Get a norm by ID"""
    result = await db.execute(
        select(Norm).options(selectinload(Norm.tags)).where(Norm.id == norm_id)
    )
    norm = result.scalar_one_or_none()
    
    if not norm:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Norm with id {norm_id} not found"
        )
    
    return NormResponse.model_validate(norm)


@router.put("/{norm_id}", response_model=NormResponse)
async def update_norm(
    norm_id: uuid.UUID,
    norm_data: NormUpdate,
    db: AsyncSession = Depends(get_db)
):
    """Update a norm"""
    result = await db.execute(
        select(Norm).options(selectinload(Norm.tags)).where(Norm.id == norm_id)
    )
    norm = result.scalar_one_or_none()
    
    if not norm:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Norm with id {norm_id} not found"
        )
    
    # Create version snapshot before update
    version_snapshot = NormVersion(
        norm_id=norm.id,
        version=norm.version,
        snapshot=_build_norm_snapshot(norm),
    )
    db.add(version_snapshot)
    
    # Update fields
    update_data = norm_data.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        if field == "tags":
            continue
        setattr(norm, field, value)
    if "tags" in update_data:
        norm.tags = [
            NormTag(tag=item["tag"], tag_category=item["tag_category"])
            for item in (update_data.get("tags") or [])
        ]
    
    # Increment version
    norm.version += 1
    
    await db.commit()
    await db.refresh(norm)
    await _invalidate_retrieve_cache()
    
    return NormResponse.model_validate(norm)


@router.delete("/{norm_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_norm(
    norm_id: uuid.UUID,
    db: AsyncSession = Depends(get_db)
):
    """Soft delete a norm by archiving it."""
    result = await db.execute(
        select(Norm).options(selectinload(Norm.tags)).where(Norm.id == norm_id)
    )
    norm = result.scalar_one_or_none()
    
    if not norm:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Norm with id {norm_id} not found"
        )
    
    norm.status = "archived"
    await db.commit()
    await _invalidate_retrieve_cache()


@router.get("/{norm_id}/versions", response_model=List[NormVersionResponse])
async def list_norm_versions(
    norm_id: uuid.UUID,
    db: AsyncSession = Depends(get_db)
):
    """Get version history for a norm"""
    result = await db.execute(
        select(NormVersion)
        .where(NormVersion.norm_id == norm_id)
        .order_by(NormVersion.version.desc())
    )
    versions = result.scalars().all()
    
    return [NormVersionResponse.model_validate(v) for v in versions]


@router.post("/{norm_id}/versions", response_model=NormVersionResponse)
async def create_norm_version(
    norm_id: uuid.UUID,
    change_note: str,
    db: AsyncSession = Depends(get_db)
):
    """Create a new version snapshot for a norm"""
    result = await db.execute(
        select(Norm).options(selectinload(Norm.tags)).where(Norm.id == norm_id)
    )
    norm = result.scalar_one_or_none()
    
    if not norm:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Norm with id {norm_id} not found"
        )
    
    version = NormVersion(
        norm_id=norm.id,
        version=norm.version,
        snapshot=_build_norm_snapshot(norm),
        change_note=change_note
    )
    
    db.add(version)
    await db.commit()
    await db.refresh(version)
    
    return NormVersionResponse.model_validate(version)
