"""
Audit Log Router
"""
from datetime import date
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from sqlalchemy import or_, desc

from core.database import get_core_db, set_rls_context
from core.security import get_current_user, validate_access_to_resource
from core.storage_s3 import get_object_bytes, list_objects_tree, should_ensure_s3_bucket
from .models import AuditLog
from .schemas import (
    AuditFileBreadcrumb,
    AuditFileBrowserResponse,
    AuditFileFolderItem,
    AuditFileObjectItem,
    AuditLogDetailResponse,
    AuditLogListResponse,
)
from api.v1.modules.a76.general_catalogs.company.models import Company

router = APIRouter()


def _audit_scope_tenant_id(db: Session, company_id: int) -> int:
    """Tenant_id de la fila ``Company`` para filtrar ``audit_logs`` (alineado con lo persistido)."""
    row = (
        db.query(Company.tenant_id)
        .filter(Company.id == company_id, Company.deleted_at.is_(None))
        .first()
    )
    if not row:
        raise HTTPException(status_code=404, detail="Company not found")
    return int(row[0])


_SEGMENT_LABELS = {
    "tenants": "Espacio",
    "companies": "Companias",
    "users": "Usuarios",
    "imports": "Importaciones",
    "csv": "Archivos CSV",
    "branding": "Logotipos",
    "certificates": "Certificados",
    "customs_brokers": "Agentes aduanales",
    "keys": "Llaves",
    "cove": "COVE",
    "doda": "DODA",
    "system": "Sistema",
    "help": "Ayuda",
}


def _normalize_relative_path(raw: Optional[str]) -> str:
    if not raw:
        return ""
    val = raw.strip().strip("/")
    if not val:
        return ""
    if ".." in val or "\\" in val:
        raise HTTPException(status_code=400, detail="Invalid path")
    parts = [p for p in val.split("/") if p]
    for part in parts:
        if part in (".", ".."):
            raise HTTPException(status_code=400, detail="Invalid path segment")
    return "/".join(parts)


def _tenant_prefix(tenant_id: int) -> str:
    return f"tenants/{tenant_id}/"


def _relative_from_tenant_prefix(key: str, tenant_prefix: str) -> str:
    if not key.startswith(tenant_prefix):
        raise HTTPException(status_code=403, detail="Access denied to object key")
    return key[len(tenant_prefix) :].strip("/")


def _companies_map(db: Session, tenant_id: int) -> Dict[str, str]:
    rows = (
        db.query(Company.id, Company.name)
        .filter(Company.tenant_id == tenant_id, Company.deleted_at.is_(None))
        .all()
    )
    out: Dict[str, str] = {}
    for company_id, company_name in rows:
        if company_id is None:
            continue
        safe_name = (company_name or "").strip()
        out[str(company_id)] = safe_name or "Compania"
    return out


def _display_segment(
    part: str,
    prev_part: Optional[str],
    company_names: Dict[str, str],
    current_user_id: Optional[str] = None,
    current_user_label: Optional[str] = None,
) -> str:
    if prev_part == "companies":
        return company_names.get(part, "Compania")
    if prev_part == "users":
        # Para carpetas de usuarios, mostrar un nombre amigable:
        # - Si es el propio usuario actual, usar preferred_username/email/nombre.
        # - Para otros IDs (UUIDs) mostrar un label genérico.
        if current_user_id and part == str(current_user_id):
            return (current_user_label or "").strip() or "Usuario"
        return "Usuario"
    if part in _SEGMENT_LABELS:
        return _SEGMENT_LABELS[part]
    # Evita exponer IDs puros en UI.
    if part.isdigit():
        return "Elemento"
    return part.replace("_", " ").strip().title() or "Elemento"


def _display_path(
    rel_path: str,
    company_names: Dict[str, str],
    current_user_id: Optional[str] = None,
    current_user_label: Optional[str] = None,
) -> str:
    if not rel_path:
        return "Raiz de archivos"
    parts = [p for p in rel_path.split("/") if p]
    labels: List[str] = []
    prev: Optional[str] = None
    for part in parts:
        labels.append(
            _display_segment(
                part,
                prev,
                company_names,
                current_user_id=current_user_id,
                current_user_label=current_user_label,
            )
        )
        prev = part
    return " / ".join(labels)


def _display_file_name(filename: str) -> str:
    stem, dot, ext = filename.rpartition(".")
    if not dot:
        stem = filename
        ext = ""
    if stem.isdigit():
        return f"Archivo{f'.{ext}' if ext else ''}"
    return filename


def _build_breadcrumbs(
    rel_path: str,
    company_names: Dict[str, str],
    current_user_id: Optional[str] = None,
    current_user_label: Optional[str] = None,
) -> List[AuditFileBreadcrumb]:
    breadcrumbs: List[AuditFileBreadcrumb] = [
        AuditFileBreadcrumb(path="", display_name="Raiz de archivos")
    ]
    if not rel_path:
        return breadcrumbs
    parts = [p for p in rel_path.split("/") if p]
    prev: Optional[str] = None
    acc: List[str] = []
    for part in parts:
        acc.append(part)
        breadcrumbs.append(
            AuditFileBreadcrumb(
                path="/".join(acc),
                display_name=_display_segment(
                    part,
                    prev,
                    company_names,
                    current_user_id=current_user_id,
                    current_user_label=current_user_label,
                ),
            )
        )
        prev = part
    return breadcrumbs

@router.get("/bitacora", response_model=AuditLogListResponse)
async def get_bitacora(
    company_id: int = Query(..., description="Company ID"),
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=100),
    search: Optional[str] = None,
    username: Optional[str] = None,
    procedure: Optional[str] = None,
    reference: Optional[str] = None,
    date_from: Optional[date] = None,
    date_to: Optional[date] = None,
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Bitácora por compañía. Requiere permiso ``audit_logs.view``.
    """
    validate_access_to_resource(
        db,
        company_id,
        current_user,
        required_permissions=["audit_logs.view"],
    )
    scope_tenant_id = _audit_scope_tenant_id(db, company_id)
    set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id)

    query = db.query(AuditLog).filter(
        AuditLog.company_id == company_id,
        AuditLog.tenant_id == scope_tenant_id,
    )

    # Filters
    if date_from:
        query = query.filter(AuditLog.date >= date_from)
    if date_to:
        query = query.filter(AuditLog.date <= date_to)
        
    if username:
        query = query.filter(AuditLog.username.ilike(f"%{username}%"))
    if procedure:
         # Exact match for dropdown filter usually better, but let's allow partial if manual
         # Legacy UI sends exact strings usually
         query = query.filter(AuditLog.procedure == procedure)
    if reference:
        query = query.filter(AuditLog.reference.ilike(f"%{reference}%"))
        
    if search:
        # General search across main columns
        search_filter = or_(
             AuditLog.reference.ilike(f"%{search}%"),
             AuditLog.procedure.ilike(f"%{search}%"),
             AuditLog.movement.ilike(f"%{search}%"),
             AuditLog.username.ilike(f"%{search}%")
        )
        query = query.filter(search_filter)

    total = query.count()
    
    # Sort by ID desc (newest first) -> Legacy usually shows newest first or spec_id desc
    logs = query.order_by(desc(AuditLog.spec_id))\
                .offset((page - 1) * page_size)\
                .limit(page_size)\
                .all()
                
    return {
        "data": logs,
        "total": total,
        "page": page,
        "page_size": page_size
    }

@router.get("/bitacora/procedimientos", response_model=List[str])
async def get_procedures(
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Lista de procedimientos para filtros (alcance compañía). Requiere ``audit_logs.view``.
    """
    validate_access_to_resource(
        db,
        company_id,
        current_user,
        required_permissions=["audit_logs.view"],
    )
    scope_tenant_id = _audit_scope_tenant_id(db, company_id)
    set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id)

    results = (
        db.query(AuditLog.procedure)
        .filter(
            AuditLog.company_id == company_id,
            AuditLog.tenant_id == scope_tenant_id,
        )
        .distinct()
        .order_by(AuditLog.procedure)
        .all()
    )
    return [r[0] for r in results if r[0]]

@router.get("/bitacora/{spec_id}/detalle", response_model=AuditLogDetailResponse)
async def get_audit_detail(
    spec_id: int,
    company_id: int = Query(..., description="Company ID"),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Detalle de un registro de bitácora. Requiere ``audit_logs.view``.
    """
    validate_access_to_resource(
        db,
        company_id,
        current_user,
        required_permissions=["audit_logs.view"],
    )
    scope_tenant_id = _audit_scope_tenant_id(db, company_id)
    set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id)

    log = (
        db.query(AuditLog)
        .filter(
            AuditLog.spec_id == spec_id,
            AuditLog.company_id == company_id,
            AuditLog.tenant_id == scope_tenant_id,
        )
        .first()
    )
    if not log:
        raise HTTPException(status_code=404, detail="Log entry not found")
    return log


@router.get("/files", response_model=AuditFileBrowserResponse)
async def list_tenant_files(
    company_id: int = Query(..., description="Company ID"),
    path: Optional[str] = Query(default="", description="Ruta relativa de navegación."),
    continuation_token: Optional[str] = Query(default=None),
    max_keys: int = Query(default=100, ge=1, le=500),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Explorador de archivos de solo lectura para Auditoría.
    Requiere permiso ``audit_logs.view``; el prefijo S3 sigue al tenant de la compañía.
    """
    if not should_ensure_s3_bucket():
        raise HTTPException(status_code=400, detail="S3 storage is disabled")

    validate_access_to_resource(
        db,
        company_id,
        current_user,
        required_permissions=["audit_logs.view"],
    )
    scope_tenant_id = _audit_scope_tenant_id(db, company_id)
    set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id)
    tenant_prefix = _tenant_prefix(scope_tenant_id)
    rel_path = _normalize_relative_path(path)
    list_prefix = f"{tenant_prefix}{rel_path}/" if rel_path else tenant_prefix

    # Datos del usuario actual para etiquetas amigables bajo /users/{id}/...
    current_user_id = str(current_user.get("sub") or "")
    current_user_label = (
        (current_user.get("preferred_username") or "").strip()
        or (current_user.get("name") or "").strip()
        or (current_user.get("email") or "").strip()
        or "Usuario"
    )

    data = list_objects_tree(
        prefix=list_prefix,
        delimiter="/",
        max_keys=max_keys,
        continuation_token=continuation_token,
    )

    company_names = _companies_map(db, scope_tenant_id)
    folders: List[AuditFileFolderItem] = []
    for prefix in data.get("prefixes", []):
        rel = _relative_from_tenant_prefix(prefix, tenant_prefix)
        folders.append(
            AuditFileFolderItem(
                path=rel,
                # Para el gestor de archivos mostramos el nombre amigable del último segmento
                # (empresa, usuario actual, etc.), no el ID bruto.
                display_name=_display_path(
                    rel,
                    company_names,
                    current_user_id=current_user_id,
                    current_user_label=current_user_label,
                ).split(" / ")[-1],
            )
        )

    files: List[AuditFileObjectItem] = []
    for obj in data.get("objects", []):
        key = obj.get("key")
        if not key:
            continue
        rel = _relative_from_tenant_prefix(key, tenant_prefix)
        name = rel.rsplit("/", 1)[-1]
        files.append(
            AuditFileObjectItem(
                path=rel,
                display_name=_display_file_name(name),
                size=int(obj.get("size", 0) or 0),
                last_modified=obj.get("last_modified"),
            )
        )

    return AuditFileBrowserResponse(
        current_path=rel_path,
        display_path=_display_path(
            rel_path,
            company_names,
            current_user_id=current_user_id,
            current_user_label=current_user_label,
        ),
        breadcrumbs=_build_breadcrumbs(
            rel_path,
            company_names,
            current_user_id=current_user_id,
            current_user_label=current_user_label,
        ),
        folders=sorted(folders, key=lambda x: x.display_name.lower()),
        files=sorted(files, key=lambda x: x.display_name.lower()),
        next_token=data.get("next_continuation_token"),
    )


@router.get("/files/download")
async def download_tenant_file(
    company_id: int = Query(..., description="Company ID"),
    path: str = Query(..., description="Ruta relativa del archivo a descargar."),
    db: Session = Depends(get_core_db),
    current_user: Dict[str, Any] = Depends(get_current_user),
):
    """
    Descarga segura (backend streaming) de archivos autorizados.
    Requiere ``audit_logs.view``.
    """
    if not should_ensure_s3_bucket():
        raise HTTPException(status_code=400, detail="S3 storage is disabled")

    validate_access_to_resource(
        db,
        company_id,
        current_user,
        required_permissions=["audit_logs.view"],
    )
    scope_tenant_id = _audit_scope_tenant_id(db, company_id)
    set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id)
    tenant_prefix = _tenant_prefix(scope_tenant_id)
    rel_path = _normalize_relative_path(path)
    if not rel_path or rel_path.endswith("/"):
        raise HTTPException(status_code=400, detail="A file path is required")

    object_key = f"{tenant_prefix}{rel_path}"
    if not object_key.startswith(tenant_prefix):
        raise HTTPException(status_code=403, detail="Access denied to object key")

    try:
        body = get_object_bytes(object_key)
    except Exception as e:
        raise HTTPException(status_code=404, detail=f"File not found: {e}") from e

    filename = rel_path.rsplit("/", 1)[-1]
    headers = {"Content-Disposition": f'attachment; filename="{filename}"'}
    return StreamingResponse(
        iter([body]),
        media_type="application/octet-stream",
        headers=headers,
    )
