import uuid
import time
from datetime import datetime
from fastapi import APIRouter, Depends, Request, HTTPException, status
from fastapi.responses import Response
from sqlalchemy.orm import Session
from app.models.database import get_db
from app.models.client import Client
from app.models.audit import AuditLog
from app.security.dependencies import get_jwt_client
from app.security.rate_limit import limiter
from app.services.mt940_parser import MT940Parser
from app.services.camt053_mapper import Camt053Mapper
from app.services.xsd_validator import XSDValidator
from app.schemas.api import ConvertRequest

router = APIRouter(tags=["convert"])

_parser = MT940Parser()
_mapper = Camt053Mapper()
_validator = XSDValidator()


@router.post("/convert")
@limiter.limit("100/minute")
def convert(
    request: Request,
    body: ConvertRequest,
    db: Session = Depends(get_db),
    client: Client = Depends(get_jwt_client),
):
    request_id = request.headers.get("X-Request-ID", str(uuid.uuid4()))
    start = time.time()
    ip = request.client.host if request.client else "unknown"

    try:
        if client.credits_remaining is not None:
            if client.credits_expire_at and datetime.utcnow() > client.credits_expire_at:
                raise HTTPException(
                    status_code=status.HTTP_402_PAYMENT_REQUIRED,
                    detail="Tu paquete de conversiones venció. Solicita uno nuevo.",
                )
            if client.credits_remaining <= 0:
                raise HTTPException(
                    status_code=status.HTTP_402_PAYMENT_REQUIRED,
                    detail="No te quedan conversiones disponibles en tu paquete. Solicita uno nuevo.",
                )

        if body.format not in ("MT940", "MT950"):
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=f"Formato no soportado: {body.format}. Use MT940 o MT950",
            )

        try:
            parsed = _parser.parse(body.message)
        except ValueError as e:
            _save_audit(db, request_id, client.id, body.format, "error", str(e), 0, ip)
            raise HTTPException(
                status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                detail=str(e),
            )

        message_id = f"MSG{request_id[:8].upper()}"
        xml_output = _mapper.map(parsed, message_id)

        if body.options.validate_output:
            valid, errors = _validator.validate(xml_output)
            if not valid:
                detail = "; ".join(errors[:3])
                _save_audit(db, request_id, client.id, body.format, "error", detail, 0, ip)
                raise HTTPException(
                    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
                    detail=f"XML generado no pasa validación XSD: {detail}",
                )

        if client.credits_remaining is not None:
            client.credits_remaining -= 1
            db.commit()

        duration_ms = int((time.time() - start) * 1000)
        _save_audit(db, request_id, client.id, body.format, "success", None, duration_ms, ip)

        headers = {
            "X-Request-ID": request_id,
            "X-Client-ID": client.id,
            "X-Conversion-Time-Ms": str(duration_ms),
        }
        return Response(
            content=xml_output,
            media_type="application/xml",
            headers=headers,
        )

    except HTTPException:
        raise
    except Exception as e:
        duration_ms = int((time.time() - start) * 1000)
        _save_audit(db, request_id, client.id, body.format, "error", str(e), duration_ms, ip)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Error interno: {str(e)}",
        )


def _save_audit(db, request_id, client_id, fmt, status_val, error_detail, duration_ms, ip):
    try:
        log = AuditLog(
            request_id=request_id,
            client_id=client_id,
            timestamp=datetime.utcnow(),
            format_in=fmt,
            status=status_val,
            error_detail=error_detail,
            duration_ms=duration_ms,
            ip_address=ip,
        )
        db.add(log)
        db.commit()
    except Exception:
        db.rollback()
