"""
Modelos de datos para el sistema de reportes mensuales
"""

from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Optional, Dict, Any, List
import json


@dataclass
class CompanyInfo:
    """Información de la empresa"""
    razon_social: str
    nombre_comercial: str


@dataclass
class DocumentStatistics:
    """Estadísticas de documentos"""
    total_documentos: int
    documentos_exitosos: int
    documentos_recibidos: int
    documentos_aceptados: int
    documentos_rechazados: int
    total_subtotal: float
    total_iva: float
    total_valor: float
    tasa_exito: float

    def to_dict(self) -> Dict[str, Any]:
        """Convierte a diccionario"""
        return {
            'total_documentos': self.total_documentos,
            'documentos_exitosos': self.documentos_exitosos,
            'documentos_recibidos': self.documentos_recibidos,
            'documentos_aceptados': self.documentos_aceptados,
            'documentos_rechazados': self.documentos_rechazados,
            'total_subtotal': self.total_subtotal,
            'total_iva': self.total_iva,
            'total_valor': self.total_valor,
            'tasa_exito': self.tasa_exito
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'DocumentStatistics':
        """Crea instancia desde diccionario"""
        return cls(
            total_documentos=data.get('total_documentos', 0),
            documentos_exitosos=data.get('documentos_exitosos', 0),
            documentos_recibidos=data.get('documentos_recibidos', 0),
            documentos_aceptados=data.get('documentos_aceptados', 0),
            documentos_rechazados=data.get('documentos_rechazados', 0),
            total_subtotal=data.get('total_subtotal', 0.0),
            total_iva=data.get('total_iva', 0.0),
            total_valor=data.get('total_valor', 0.0),
            tasa_exito=data.get('tasa_exito', 0.0)
        )

    @classmethod
    def empty(cls) -> 'DocumentStatistics':
        """Crea instancia vacía"""
        return cls(
            total_documentos=0,
            documentos_exitosos=0,
            documentos_recibidos=0,
            documentos_aceptados=0,
            documentos_rechazados=0,
            total_subtotal=0.0,
            total_iva=0.0,
            total_valor=0.0,
            tasa_exito=0.0
        )

    def combine_with(self, other: 'DocumentStatistics') -> 'DocumentStatistics':
        """Combina con otra estadística"""
        combined = DocumentStatistics(
            total_documentos=self.total_documentos + other.total_documentos,
            documentos_exitosos=self.documentos_exitosos + other.documentos_exitosos,
            documentos_recibidos=self.documentos_recibidos + other.documentos_recibidos,
            documentos_aceptados=self.documentos_aceptados + other.documentos_aceptados,
            documentos_rechazados=self.documentos_rechazados + other.documentos_rechazados,
            total_subtotal=self.total_subtotal + other.total_subtotal,
            total_iva=self.total_iva + other.total_iva,
            total_valor=self.total_valor + other.total_valor,
            tasa_exito=0.0  # Se calculará después
        )
        
        # Recalcular tasa de éxito
        if combined.total_documentos > 0:
            combined.tasa_exito = round(
                (combined.documentos_exitosos / combined.total_documentos) * 100, 2
            )
        
        return combined


@dataclass
class MonthlyReport:
    """Reporte mensual completo"""
    id: Optional[int]
    periodo: str
    fecha_envio: datetime
    total_documentos: int
    documentos_exitosos: int
    valor_total: float
    tasa_exito: float
    observaciones: Optional[str]
    status: str
    contenido_completo: Optional[str]
    detalle_facturas: Optional[DocumentStatistics]
    detalle_gastos: Optional[DocumentStatistics]

    def to_dict(self) -> Dict[str, Any]:
        """Convierte a diccionario para JSON"""
        return {
            'id': self.id,
            'periodo': self.periodo,
            'fecha_envio': self.fecha_envio.isoformat() if self.fecha_envio else None,
            'total_documentos': self.total_documentos,
            'documentos_exitosos': self.documentos_exitosos,
            'valor_total': self.valor_total,
            'tasa_exito': self.tasa_exito,
            'observaciones': self.observaciones,
            'status': self.status,
            'contenido_completo': self.contenido_completo,
            'detalle_facturas': self.detalle_facturas.to_dict() if self.detalle_facturas else None,
            'detalle_gastos': self.detalle_gastos.to_dict() if self.detalle_gastos else None
        }

    @classmethod
    def from_db_row(cls, row: Dict[str, Any]) -> 'MonthlyReport':
        """Crea instancia desde fila de base de datos"""
        detalle_facturas = None
        detalle_gastos = None
        
        # Parsear detalles JSON si existen
        if row.get('detalle_facturas'):
            try:
                facturas_data = json.loads(row['detalle_facturas'])
                detalle_facturas = DocumentStatistics.from_dict(facturas_data)
            except (json.JSONDecodeError, KeyError):
                pass
        
        if row.get('detalle_gastos'):
            try:
                gastos_data = json.loads(row['detalle_gastos'])
                detalle_gastos = DocumentStatistics.from_dict(gastos_data)
            except (json.JSONDecodeError, KeyError):
                pass
        
        return cls(
            id=row.get('id'),
            periodo=row.get('periodo', ''),
            fecha_envio=row.get('fecha_envio') or datetime.now(),
            total_documentos=row.get('total_documentos', 0),
            documentos_exitosos=row.get('documentos_exitosos', 0),
            valor_total=float(row.get('valor_total', 0)),
            tasa_exito=float(row.get('tasa_exito', 0)),
            observaciones=row.get('observaciones'),
            status=row.get('status', 'pending'),
            contenido_completo=row.get('contenido_completo'),
            detalle_facturas=detalle_facturas,
            detalle_gastos=detalle_gastos
        )


@dataclass
class SystemStatus:
    """Estado del sistema"""
    db_connected: bool
    last_report: Optional[Dict[str, Any]]
    next_report_date: Optional[datetime]
    current_time: datetime
    error: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convierte a diccionario para JSON"""
        return {
            'db_connected': self.db_connected,
            'last_report': self.last_report,
            'next_report_date': self.next_report_date.isoformat() if self.next_report_date else None,
            'current_time': self.current_time.isoformat(),
            'error': self.error
        }


@dataclass
class ReportGenerationResult:
    """Resultado de generación de reporte"""
    success: bool
    message: str
    period: Optional[str]
    report_id: Optional[int]
    statistics: Optional[DocumentStatistics]
    error: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convierte a diccionario para JSON"""
        return {
            'success': self.success,
            'message': self.message,
            'period': self.period,
            'report_id': self.report_id,
            'statistics': self.statistics.to_dict() if self.statistics else None,
            'error': self.error
        }


@dataclass
class MissingReportsResult:
    """Resultado de verificación de reportes faltantes"""
    success: bool
    generated_count: int
    generated_periods: List[str]
    message: str
    error: Optional[str] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convierte a diccionario para JSON"""
        return {
            'success': self.success,
            'generated_count': self.generated_count,
            'generated_periods': self.generated_periods,
            'message': self.message,
            'error': self.error
        }


class ReportStatus:
    """Constantes para estados de reportes"""
    COMPLETED = 'completed'
    PENDING = 'pending'
    ERROR = 'error'
    PROCESSING = 'processing'


class DocumentType:
    """Constantes para tipos de documentos"""
    INVOICE = 'invoice'
    EXPENSE = 'expense'
    ALL = 'all'


def format_currency(amount: float) -> str:
    """Formatea un valor como moneda colombiana"""
    return f"${amount:,.2f}"


def format_percentage(value: float) -> str:
    """Formatea un valor como porcentaje"""
    return f"{value:.2f}%"


def format_period(period: str) -> str:
    """Formatea un período YYYY-MM a texto legible"""
    if not period or len(period) != 7:
        return period
    
    year, month = period.split('-')
    month_names = [
        'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
        'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'
    ]
    
    try:
        month_name = month_names[int(month) - 1]
        return f"{month_name} {year}"
    except (ValueError, IndexError):
        return period


def validate_period(period: str) -> bool:
    """Valida que un período tenga el formato correcto YYYY-MM"""
    if not period or len(period) != 7:
        return False
    
    try:
        year, month = period.split('-')
        year_int = int(year)
        month_int = int(month)
        
        return (2000 <= year_int <= 2100 and 1 <= month_int <= 12)
    except (ValueError, AttributeError):
        return False


def get_previous_month_period() -> str:
    """Obtiene el período del mes anterior en formato YYYY-MM"""
    today = datetime.now()
    if today.month == 1:
        return f"{today.year - 1}-12"
    else:
        return f"{today.year}-{today.month - 1:02d}"


def get_period_date_range(period: str) -> tuple[datetime, datetime]:
    """Obtiene el rango de fechas para un período dado"""
    if not validate_period(period):
        raise ValueError(f"Período inválido: {period}")
    
    year, month = period.split('-')
    year_int = int(year)
    month_int = int(month)
    
    start_date = datetime(year_int, month_int, 1)
    
    # Último día del mes
    if month_int == 12:
        next_month = datetime(year_int + 1, 1, 1)
    else:
        next_month = datetime(year_int, month_int + 1, 1)
    
    end_date = next_month - timedelta(days=1)
    end_date = end_date.replace(hour=23, minute=59, second=59)
    
    return start_date, end_date


def calculate_success_rate(total: int, successful: int) -> float:
    """Calcula la tasa de éxito"""
    if total <= 0:
        return 0.0
    return round((successful / total) * 100, 2)


def generate_period_list(start_year: int, start_month: int, 
                        end_year: int, end_month: int) -> List[str]:
    """Genera una lista de períodos entre dos fechas"""
    periods = []
    current_year = start_year
    current_month = start_month
    
    while (current_year < end_year or 
           (current_year == end_year and current_month <= end_month)):
        periods.append(f"{current_year}-{current_month:02d}")
        
        current_month += 1
        if current_month > 12:
            current_month = 1
            current_year += 1
    
    return periods


def get_missing_periods(existing_periods: List[str], 
                       start_year: int, end_year: int, 
                       end_month: int = 12) -> List[str]:
    """Obtiene los períodos faltantes en un rango dado"""
    if end_year == datetime.now().year:
        # Para el año actual, solo hasta el mes anterior
        end_month = min(end_month, datetime.now().month - 1)
    
    all_periods = []
    
    # Generar todos los períodos requeridos
    for year in range(start_year, end_year + 1):
        start_month = 1
        final_month = 12 if year < end_year else end_month
        
        for month in range(start_month, final_month + 1):
            all_periods.append(f"{year}-{month:02d}")
    
    # Filtrar los que no existen
    existing_set = set(existing_periods)
    missing = [period for period in all_periods if period not in existing_set]
    
    return missing


class DatabaseError(Exception):
    """Excepción para errores de base de datos"""
    pass


class ReportError(Exception):
    """Excepción para errores de generación de reportes"""
    pass


class ValidationError(Exception):
    """Excepción para errores de validación"""
    pass