#!/usr/bin/env python3
"""
Clase base para scripts de descarga de boletines provinciales.

Cada script provincial hereda de BaseDescargador e implementa únicamente
descargar_fecha(), con la lógica HTTP específica del sitio.
El loop de fechas, argparse, resumen y skip de archivos existentes
están centralizados aquí.
"""

import argparse
import calendar
import datetime
import logging
import os
import sys
from abc import ABC, abstractmethod
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent))
from helpers.db_resumen import guardar_dia_faltante

# Logger a stdout — el padre captura stdout como subprocess y lo re-loguea
logging.basicConfig(
    stream=sys.stdout,
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%H:%M:%S",
)


def ya_existe_en_provincia(directorio: str, nombre_archivo: str) -> bool:
    """Busca nombre_archivo en todas las subcarpetas del directorio padre (la provincia)."""
    provincia_dir = Path(directorio).parent
    return any(f.name == nombre_archivo for f in provincia_dir.rglob("*.pdf") if f.name == nombre_archivo)


class BaseDescargador(ABC):
    # Nombre para mostrar en cabecera y mensajes
    NOMBRE: str

    # Directorio destino por defecto (override con --directorio)
    DIRECTORIO: str

    # Días de la semana válidos (0=lunes … 6=domingo). Default: lunes a viernes.
    DIAS_VALIDOS: list[int] = [0, 1, 2, 3, 4]

    # ------------------------------------------------------------------ #
    # Métodos que los subclases pueden sobreescribir                       #
    # ------------------------------------------------------------------ #

    def nombre_archivo(self, fecha: datetime.date) -> str:
        """Nombre del PDF para una fecha. Sobreescribir si el formato difiere."""
        return f"boletin_{fecha.strftime('%Y-%m-%d')}.pdf"

    def es_dia_valido(self, fecha: datetime.date) -> bool:
        return fecha.weekday() in self.DIAS_VALIDOS

    # ------------------------------------------------------------------ #
    # Método abstracto que cada provincia debe implementar                 #
    # ------------------------------------------------------------------ #

    @abstractmethod
    def descargar_fecha(self, fecha: datetime.date, directorio: str) -> str:
        """
        Descarga el boletín de la fecha indicada y lo guarda en directorio.
        Retorna:
          'ok'             — descarga exitosa
          'no_disponible'  — boletín no existe en el sitio (404, no listado, etc.)
          'error_descarga' — error de red/HTTP al intentar descargar
        El nombre del archivo guardado debe coincidir con nombre_archivo(fecha).
        """
        ...

    # ------------------------------------------------------------------ #
    # Lógica común (no sobreescribir salvo casos muy especiales)           #
    # ------------------------------------------------------------------ #

    def run(self) -> None:
        args = self._parsear_args()

        try:
            fecha_inicio, fecha_fin = _calcular_rango_fechas(args.anio, args.mes, args.dia)
        except ValueError as e:
            logging.error(f"Error: {e}")
            sys.exit(1)

        logging.info(f"INICIO | {self.NOMBRE} | directorio={args.directorio}")

        try:
            os.makedirs(args.directorio, exist_ok=True)
        except Exception as e:
            logging.error(f"Error al crear el directorio: {e}")
            sys.exit(1)

        if fecha_inicio == fecha_fin:
            logging.info(f"Procesando {fecha_inicio.strftime('%Y-%m-%d')}")
        else:
            logging.info(f"Procesando desde {fecha_inicio.strftime('%Y-%m-%d')} hasta {fecha_fin.strftime('%Y-%m-%d')}")

        fecha_actual = fecha_inicio
        total = descargados = no_disponibles = error_descarga_count = dias_omitidos = 0

        while fecha_actual <= fecha_fin:
            if not self.es_dia_valido(fecha_actual):
                dias_omitidos += 1
                fecha_actual += datetime.timedelta(days=1)
                continue

            total += 1
            logging.info(f"[{total}] {fecha_actual.strftime('%Y-%m-%d')} ({fecha_actual.strftime('%A')})")

            if ya_existe_en_provincia(args.directorio, self.nombre_archivo(fecha_actual)):
                logging.info(f"  Ya existe, saltando...")
                descargados += 1
            else:
                resultado = self.descargar_fecha(fecha_actual, args.directorio)
                if resultado == 'ok':
                    descargados += 1
                elif resultado == 'error_descarga':
                    error_descarga_count += 1
                    guardar_dia_faltante(self.NOMBRE, fecha_actual.isoformat(), 'error_descarga')
                else:
                    no_disponibles += 1
                    guardar_dia_faltante(self.NOMBRE, fecha_actual.isoformat(), 'no_disponible')

            fecha_actual += datetime.timedelta(days=1)

        logging.info(
            f"RESUMEN | {self.NOMBRE} | "
            f"procesados={total} omitidos={dias_omitidos} "
            f"descargados={descargados} no_disponibles={no_disponibles} "
            f"error_descarga={error_descarga_count}"
        )

    def _parsear_args(self) -> argparse.Namespace:
        parser = argparse.ArgumentParser(
            description=f"Descarga boletines oficiales de {self.NOMBRE}",
        )
        parser.add_argument("--anio", type=int, help="Año a descargar")
        parser.add_argument("--mes", type=int, help="Mes a descargar (1-12)")
        parser.add_argument("--dia", type=int, help="Día a descargar (1-31)")
        parser.add_argument("--directorio", type=str, default=self.DIRECTORIO,
                            help=f"Directorio de destino (por defecto: {self.DIRECTORIO})")
        args = parser.parse_args()

        if args.mes is not None and (args.mes < 1 or args.mes > 12):
            parser.error("El mes debe estar entre 1 y 12")
        if args.dia is not None and (args.dia < 1 or args.dia > 31):
            parser.error("El día debe estar entre 1 y 31")
        if args.dia is not None and args.mes is None:
            parser.error("Si especificas un día, debes especificar también el mes")
        if args.mes is not None and args.anio is None:
            parser.error("Si especificas un mes, debes especificar también el año")

        return args


# ------------------------------------------------------------------ #
# Función auxiliar (no forma parte de la clase)                        #
# ------------------------------------------------------------------ #

def _calcular_rango_fechas(anio=None, mes=None, dia=None):
    hoy = datetime.date.today()
    if anio is None and mes is None and dia is None:
        return (hoy, hoy)
    if anio is not None and mes is None and dia is None:
        return (datetime.date(anio, 1, 1), datetime.date(anio, 12, 31))
    if anio is not None and mes is not None and dia is None:
        ultimo = calendar.monthrange(anio, mes)[1]
        return (datetime.date(anio, mes, 1), datetime.date(anio, mes, ultimo))
    if anio is not None and mes is not None and dia is not None:
        return (datetime.date(anio, mes, dia), datetime.date(anio, mes, dia))
    raise ValueError("Combinación de parámetros inválida")
