"""
Audit Log Service
"""
import logging
from datetime import datetime, timedelta, date, time
from decimal import Decimal
import uuid
import pytz
from typing import Optional, List, Dict, Any
from sqlalchemy.orm import Session
from ..models import AuditLog
from .core import AuditMapper, ReferenceGenerator

logger = logging.getLogger(__name__)


def _make_json_safe(obj: Any) -> Any:
    """Recursively convert non-JSON-serializable types to serializable equivalents."""
    if obj is None:
        return None
    if isinstance(obj, dict):
        return {k: _make_json_safe(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple)):
        return [_make_json_safe(v) for v in obj]
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, date):
        return obj.isoformat()
    if isinstance(obj, time):
        return obj.isoformat()
    if isinstance(obj, Decimal):
        return float(obj)
    if isinstance(obj, uuid.UUID):
        return str(obj)
    if isinstance(obj, bytes):
        return obj.decode("utf-8", errors="replace")
        
    if hasattr(obj, "__dict__"):
        d = dict(obj.__dict__)
        d.pop("_sa_instance_state", None)
        return {k: _make_json_safe(v) for k, v in d.items()}
        
    if not isinstance(obj, (int, float, str, bool)):
        return str(obj)
        
    return obj


class AuditService:
    
    @staticmethod
    def create_audit_log(
        db: Session,
        reference: str,
        procedure: str,
        movement: str,
        username: str,
        system: str = "SCAF",
        # Extra context
        table_name: Optional[str] = None,
        record_id: Optional[str] = None,
        operation_type: Optional[str] = None,
        old_values: Optional[Dict] = None,
        new_values: Optional[Dict] = None,
        changed_fields: Optional[List[str]] = None,
        # HTTP Context
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
        endpoint: Optional[str] = None,
        request_method: Optional[str] = None,
        session_id: Optional[str] = None,
        company_id: Optional[int] = None,
        tenant_id: Optional[int] = None,
    ) -> AuditLog:
        """
        Low-level creation of an Audit Log entry
        """
        # Timezone handling: Use UTC for consistency across regions.
        # Frontend will convert to user's local time.
        now = datetime.now(pytz.UTC)
        
        log = AuditLog(
            reference=reference,
            procedure=procedure,
            movement=movement,
            username=username,
            date=now.date(),
            time=now.time(),
            timestamp=now,
            system=system,
            table_name=table_name,
            record_id=record_id,
            operation_type=operation_type,
            old_values=_make_json_safe(old_values),
            new_values=_make_json_safe(new_values),
            changed_fields=changed_fields,
            ip_address=ip_address,
            user_agent=user_agent,
            endpoint=endpoint,
            request_method=request_method,
            session_id=session_id,
            company_id=company_id,
            tenant_id=tenant_id
        )
        
        db.add(log)
        db.flush()
        return log

    @staticmethod
    def log_crud_operation(
        db: Session,
        table_name: str,
        operation_type: str,
        record_data: Dict[str, Any],
        username: str,
        record_id: Optional[str] = None,
        old_values: Optional[Dict] = None,
        new_values: Optional[Dict] = None,
        # Context
        ip_address: Optional[str] = None,
        user_agent: Optional[str] = None,
        company_id: Optional[int] = None,
        tenant_id: Optional[int] = None
    ):
        """
        High-level wrapper to log CRUD operations automatically mapping to Legacy format
        """
        
        # 1. Map to Legacy Base Format
        legacy_data = AuditMapper.map_to_legacy_format(
            table_name=table_name,
            record_id=record_id,
            operation_type=operation_type,
            username=username
        )
        
        # 2. Refine Reference based on specific table logic
        reference = legacy_data["reference"]
        
        if table_name == "invoice_header":
             generated_ref = ReferenceGenerator.generate_invoice_reference(record_data)
             # Use generated ref only if meaningful, else keep default
             if generated_ref != "FUSE0-000-00": 
                 reference = generated_ref
                 
        elif table_name == "pedimentos":
            reference = ReferenceGenerator.generate_pedimento_reference(record_data)
        
        elif table_name == "clients_and_providers":
            reference = record_data.get("rfc") or reference
            
        elif table_name == "parts":
            reference = record_data.get("part_number") or reference
            
        elif table_name in {"companies", "company"}:
            reference = record_data.get("rfc") or reference
            
        elif table_name == "classes":
            reference = record_data.get("class_code") or reference


        # 3. Detect Changed Fields (for Update)
        changed_fields = None
        if operation_type == "UPDATE" and old_values and new_values:
            changed_fields = [
                k for k in new_values.keys() 
                if old_values.get(k) != new_values.get(k)
            ]

        # 4. Create Log
        return AuditService.create_audit_log(
            db=db,
            reference=reference,
            procedure=legacy_data["procedure"],
            movement=legacy_data["movement"],
            username=username,
            system=legacy_data["system"],
            table_name=table_name,
            record_id=record_id,
            operation_type=operation_type,
            old_values=old_values,
            new_values=new_values,
            changed_fields=changed_fields,
            ip_address=ip_address,
            user_agent=user_agent,
            company_id=company_id,
            tenant_id=tenant_id
        )

    @staticmethod
    def log_login(
        db: Session,
        username: str,
        ip_address: str = None,
        user_agent: str = None,
        company_id: Optional[int] = None,
        tenant_id: Optional[int] = None,
    ):
        """
        ``audit_logs`` exige ``tenant_id`` y ``company_id``. El login vía Hub no
        define compañía activa; sin ambos argumentos no se inserta fila (antes fallaba NOT NULL).
        """
        if company_id is None or tenant_id is None:
            return None

        try:
            now = datetime.now(pytz.UTC)
            five_seconds_ago = now - timedelta(seconds=5)
            existing = db.query(AuditLog).filter(
                AuditLog.username == username,
                AuditLog.operation_type == "LOGIN",
                AuditLog.company_id == company_id,
                AuditLog.tenant_id == tenant_id,
                AuditLog.timestamp >= five_seconds_ago,
            ).first()
            if existing:
                return existing
        except Exception as e:
            logger.warning("Login audit debounce query failed: %s", e)

        return AuditService.create_audit_log(
            db=db,
            reference="LOGIN",
            procedure="SYSTEM SCAF",
            movement="SYSTEM LOGIN",
            username=username,
            operation_type="LOGIN",
            ip_address=ip_address,
            user_agent=user_agent,
            company_id=company_id,
            tenant_id=tenant_id,
        )

    @staticmethod
    def log_logout(
        db: Session,
        username: str,
        ip_address: str = None,
        user_agent: str = None,
        company_id: Optional[int] = None,
        tenant_id: Optional[int] = None,
    ):
        """Misma condición que ``log_login``: sin alcance compañía/tenant no se escribe."""
        if company_id is None or tenant_id is None:
            return None
        try:
            AuditService.create_audit_log(
                db=db,
                reference="LOGOUT",
                procedure="SYSTEM AUTH",
                movement="SYSTEM LOGOUT",
                username=username,
                operation_type="LOGOUT",
                ip_address=ip_address,
                user_agent=user_agent,
                company_id=company_id,
                tenant_id=tenant_id,
            )
        except Exception as e:
            logger.warning("Logout audit insert failed: %s", e)
