"""
agente_fundamental.py — Agente Fundamental de Leonex.

Lee las plantillas Excel de la carpeta fundamental/plantillas/,
detecta el ticker por el nombre del archivo, descarga los datos
financieros de SEC EDGAR y rellena las hojas TIKR_IS, TIKR_BS
y TIKR_CF con los valores correctos.

Uso:
    python agents/agente_fundamental.py              # procesa todas las plantillas
    python agents/agente_fundamental.py --ticker MSFT  # solo una empresa
    python agents/agente_fundamental.py --check        # comprueba nuevos filings
"""

from __future__ import annotations

import argparse
import json
import logging
import re
import sys
from datetime import date, datetime, UTC
from pathlib import Path
from typing import Optional

# Rutas del proyecto
PROJECT_ROOT = Path(__file__).resolve().parents[1]
FUNDAMENTAL_DIR = PROJECT_ROOT / "fundamental"
PLANTILLAS_DIR = FUNDAMENTAL_DIR / "plantillas"
OUTPUT_DIR = FUNDAMENTAL_DIR / "output"
TICKER_MAP_PATH = FUNDAMENTAL_DIR / "ticker_map.json"
LOGS_DIR = PROJECT_ROOT / "logs"
DASHBOARD_DATA_DIR = PROJECT_ROOT / "dashboard" / "data"

# Añadir el directorio fundamental al path para importar los módulos
sys.path.insert(0, str(FUNDAMENTAL_DIR))

from edgar_xbrl import extract_annual_financials, get_fiscal_year_end_dates
from edgar_text import get_latest_8k_text, get_latest_10q_filing, check_new_filing_since
from excel_writer import write_financials_to_excel


def build_logger() -> logging.Logger:
    LOGS_DIR.mkdir(parents=True, exist_ok=True)
    logger = logging.getLogger("agente_fundamental")
    logger.setLevel(logging.INFO)
    logger.handlers.clear()
    fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(name)s | %(message)s")
    fh = logging.FileHandler(LOGS_DIR / "agente_fundamental.log", encoding="utf-8")
    fh.setFormatter(fmt)
    sh = logging.StreamHandler()
    sh.setFormatter(fmt)
    logger.addHandler(fh)
    logger.addHandler(sh)
    return logger


log = build_logger()


def load_ticker_map() -> dict:
    with open(TICKER_MAP_PATH, encoding="utf-8") as f:
        return json.load(f)


def detect_ticker_from_filename(filename: str, known_tickers: list[str]) -> Optional[str]:
    """
    Detecta el ticker en el nombre de archivo.
    Busca coincidencias exactas de ticker (case-insensitive).
    """
    name_upper = filename.upper()
    for ticker in known_tickers:
        # Buscar el ticker como palabra completa (no parte de otra)
        pattern = r"\b" + re.escape(ticker.upper()) + r"\b"
        if re.search(pattern, name_upper):
            return ticker
    return None


def find_plantillas(ticker_filter: Optional[str] = None) -> list[tuple[Path, str]]:
    """
    Escanea la carpeta plantillas/ y devuelve lista de (ruta, ticker).
    Si ticker_filter no es None, solo devuelve esa empresa.
    """
    ticker_map = load_ticker_map()
    known_tickers = list(ticker_map.keys())
    results = []

    for path in sorted(PLANTILLAS_DIR.glob("*.xlsx")):
        if path.name.startswith("~"):  # archivo abierto en Excel
            continue
        ticker = detect_ticker_from_filename(path.stem, known_tickers)
        if ticker is None:
            log.warning("No se pudo detectar ticker en '%s' — omitiendo", path.name)
            continue
        if ticker_filter and ticker != ticker_filter.upper():
            continue
        results.append((path, ticker))
        log.info("Plantilla detectada: %s → %s", path.name, ticker)

    return results


def extract_guidance_with_llm(text: str, ticker: str) -> Optional[dict]:
    """
    Usa Ollama (qwen2.5-coder:7b) para extraer guidance del texto del 8-K.
    Devuelve None si Ollama no está disponible.
    """
    try:
        import requests as req
        prompt = f"""You are an expert financial analyst. Analyze this earnings release from {ticker} and extract the following data as JSON:

{{
  "guidance_revenue_next_q_low": <number in millions or null>,
  "guidance_revenue_next_q_high": <number in millions or null>,
  "guidance_eps_next_q": <number or null>,
  "guidance_margin": <percentage as decimal or null>,
  "capex_guidance": <number in millions or null>,
  "buyback_announced": <number in millions or null>,
  "key_positives": ["<list of positive factors mentioned>"],
  "key_risks": ["<list of risks or warnings mentioned>"],
  "confidence_score": <1-10>
}}

Text (first 6000 chars):
{text[:6000]}

Respond ONLY with valid JSON, no additional text."""

        resp = req.post(
            "http://localhost:11434/api/generate",
            json={"model": "qwen2.5-coder:7b", "prompt": prompt, "stream": False},
            timeout=120,
        )
        if resp.ok:
            raw = resp.json().get("response", "")
            # Extraer el JSON del response
            json_match = re.search(r"\{.*\}", raw, re.DOTALL)
            if json_match:
                return json.loads(json_match.group())
    except Exception as exc:
        log.warning("Ollama no disponible o error: %s", exc)
    return None


def process_ticker(ticker: str, template_path: Path, ticker_map: dict) -> bool:
    """
    Procesa una empresa: extrae XBRL de EDGAR, extrae guidance del 8-K,
    y escribe los datos en la plantilla Excel.
    """
    config = ticker_map.get(ticker)
    if not config:
        log.error("Ticker %s no encontrado en ticker_map.json", ticker)
        return False

    cik = config["cik"]
    fiscal_year_end_month = config["fiscal_year_end_month"]
    fiscal_years = config["fiscal_years"]

    log.info("=" * 55)
    log.info("Procesando %s (%s)", ticker, config["name"])
    log.info("CIK: %s | FY end mes: %d", cik, fiscal_year_end_month)
    log.info("=" * 55)

    # ── Capa 1: Datos XBRL ───────────────────────────────────────────────
    log.info("[CAPA 1] Descargando datos XBRL de EDGAR...")
    fy_end_dates = get_fiscal_year_end_dates(fiscal_year_end_month, fiscal_years)

    try:
        annual_data = extract_annual_financials(cik, fy_end_dates)
    except Exception as exc:
        log.error("Error descargando XBRL para %s: %s", ticker, exc)
        return False

    # Resumen de cobertura
    for fy_end, data in annual_data.items():
        n_ok = sum(1 for v in data.values() if v is not None)
        n_total = len(data)
        log.info("  %s: %d/%d conceptos con datos", fy_end, n_ok, n_total)

    # ── Capa 2: Guidance del 8-K (+ LLM si disponible) ────────────────────
    log.info("[CAPA 2] Buscando earnings release (8-K) más reciente...")
    guidance_data = None

    try:
        latest_8k = get_latest_8k_text(cik)
        if latest_8k:
            log.info("  8-K encontrado: filed=%s, period=%s", latest_8k["filed"], latest_8k["period"])
            log.info("  Texto: %d caracteres", len(latest_8k["text"]))
            guidance_data = extract_guidance_with_llm(latest_8k["text"], ticker)
            if guidance_data:
                guidance_data["source_date"] = latest_8k["filed"]
                log.info("  Guidance extraído por LLM (confianza: %s/10)",
                         guidance_data.get("confidence_score", "?"))
            else:
                log.info("  LLM no disponible — guidance no extraído")
        else:
            log.info("  No se encontró 8-K reciente con texto")
    except Exception as exc:
        log.warning("Error en Capa 2 (8-K/LLM): %s", exc)

    # ── Escribir en Excel ──────────────────────────────────────────────────
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    today = date.today().isoformat()
    output_filename = f"{ticker}_actualizado_{today}.xlsx"
    output_path = OUTPUT_DIR / output_filename

    log.info("[EXCEL] Escribiendo datos en: %s", output_path)
    try:
        write_financials_to_excel(
            template_path=str(template_path),
            output_path=str(output_path),
            annual_data=annual_data,
            guidance_data=guidance_data,
        )
        log.info("[OK] %s procesado correctamente → %s", ticker, output_filename)
        return True
    except Exception as exc:
        log.error("Error escribiendo Excel para %s: %s", ticker, exc)
        return False


def check_new_filings(ticker_map: dict) -> list[dict]:
    """
    Comprueba si hay nuevos 10-Q, 10-K o 8-K desde la última ejecución.
    """
    log.info("Comprobando nuevos filings en EDGAR...")
    alerts = []

    # Leer fecha de última ejecución
    last_run_file = FUNDAMENTAL_DIR / ".last_run"
    if last_run_file.exists():
        since = last_run_file.read_text().strip()
    else:
        since = "2025-01-01"

    for ticker, config in ticker_map.items():
        try:
            new = check_new_filing_since(config["cik"], since)
            if new:
                log.info("  %s: %d nuevo(s) filing(s) desde %s", ticker, len(new), since)
                for f in new:
                    log.info("    %s | %s | %s", f["form"], f["filed"], f["period"])
                alerts.extend([{"ticker": ticker, **f} for f in new])
        except Exception as exc:
            log.warning("  Error comprobando %s: %s", ticker, exc)

    # Actualizar fecha de última ejecución
    last_run_file.write_text(date.today().isoformat())

    return alerts


def export_dashboard_json(results: list[dict]) -> None:
    """Exporta un resumen al dashboard de Leonex."""
    DASHBOARD_DATA_DIR.mkdir(parents=True, exist_ok=True)
    payload = {
        "generated_at": datetime.now(UTC).isoformat(),
        "results": results,
    }
    out = DASHBOARD_DATA_DIR / "fundamentals_report.json"
    out.write_text(json.dumps(payload, indent=2, ensure_ascii=False), encoding="utf-8")
    log.info("Dashboard JSON exportado → %s", out)


def main() -> None:
    parser = argparse.ArgumentParser(description="Leonex Agente Fundamental")
    parser.add_argument("--ticker", help="Procesar solo este ticker (ej: MSFT)")
    parser.add_argument("--check", action="store_true", help="Solo comprobar nuevos filings")
    args = parser.parse_args()

    ticker_map = load_ticker_map()

    # ── Modo check: solo verificar nuevos filings ──────────────────────────
    if args.check:
        alerts = check_new_filings(ticker_map)
        if alerts:
            print(f"\n[!] {len(alerts)} nuevo(s) filing(s) detectado(s):")
            for a in alerts:
                print(f"    {a['ticker']} | {a['form']} | {a['filed']} | periodo: {a['period']}")
        else:
            print("\n[OK] No hay filings nuevos desde la última ejecución.")
        return

    # ── Modo normal: procesar plantillas ──────────────────────────────────
    plantillas = find_plantillas(ticker_filter=args.ticker)

    if not plantillas:
        if args.ticker:
            print(f"\n[!] No se encontró plantilla para {args.ticker} en:")
            print(f"    {PLANTILLAS_DIR}")
            print(f"\n    Asegúrate de que el nombre del archivo contiene '{args.ticker}'")
        else:
            print(f"\n[!] No hay plantillas Excel en {PLANTILLAS_DIR}")
            print(f"    Añade tus plantillas ahí (el ticker debe aparecer en el nombre)")
        return

    results = []
    ok = 0
    for template_path, ticker in plantillas:
        success = process_ticker(ticker, template_path, ticker_map)
        results.append({
            "ticker": ticker,
            "template": template_path.name,
            "status": "OK" if success else "ERROR",
            "output": f"{ticker}_actualizado_{date.today().isoformat()}.xlsx" if success else None,
        })
        if success:
            ok += 1

    export_dashboard_json(results)

    # Fijar stdout a UTF-8 en Windows para evitar UnicodeEncodeError
    if sys.stdout.encoding.lower() != "utf-8":
        import io
        sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")

    print(f"\n{'='*55}")
    print(f"Leonex -- Agente Fundamental completado")
    print(f"{'='*55}")
    print(f"Plantillas procesadas : {len(plantillas)}")
    print(f"Exitosas              : {ok}")
    print(f"Errores               : {len(plantillas) - ok}")
    print(f"{'-'*55}")
    for r in results:
        badge = "[OK]" if r["status"] == "OK" else "[!!]"
        print(f"{badge} {r['ticker']:<6} | {r['template']}")
        if r["output"]:
            print(f"       -> fundamental/output/{r['output']}")
    print(f"{'='*55}")
    print(f"Archivos actualizados en: {OUTPUT_DIR}")


if __name__ == "__main__":
    main()
