"""
edgar_xbrl.py — Capa 1 del Agente Fundamental de Leonex.

Descarga los datos financieros estructurados (XBRL) de SEC EDGAR
para una empresa y los devuelve como un diccionario organizado por
año fiscal listo para escribir en la plantilla Excel.

Fuente: https://data.sec.gov/api/xbrl/companyfacts/{CIK}.json
Sin API key. Gratuita y oficial.
"""

from __future__ import annotations

import logging
import time
from datetime import date
from functools import lru_cache
from typing import Optional

import requests

log = logging.getLogger("edgar_xbrl")

EDGAR_FACTS_URL = "https://data.sec.gov/api/xbrl/companyfacts/{cik}.json"
HEADERS = {"User-Agent": "Leonex-Fundamental-Agent research@leonex.local"}

# ── Mapeado: nombre interno → lista de conceptos XBRL en orden de preferencia
# El agente intenta cada concepto en orden y usa el primero que encuentre datos.
XBRL_MAP: dict[str, list[str]] = {
    # ── Income Statement ──────────────────────────────────────────────────
    "revenues": [
        "RevenueFromContractWithCustomerExcludingAssessedTax",
        "Revenues",
        "SalesRevenueNet",
        "SalesRevenueGoodsNet",
    ],
    "cogs": [
        "CostOfGoodsAndServicesSold",
        "CostOfRevenue",
        "CostOfGoodsSold",
    ],
    "gross_profit": ["GrossProfit"],
    "sga": ["SellingGeneralAndAdministrativeExpense"],
    "rd": ["ResearchAndDevelopmentExpense"],
    "operating_expenses": ["OperatingExpenses"],
    "operating_income": ["OperatingIncomeLoss"],
    "interest_expense": [
        "InterestExpense",
        "InterestAndDebtExpense",
    ],
    "interest_income": [
        "InvestmentIncomeInterest",
        "InterestAndInvestmentIncome",
    ],
    "fx_gain_loss": ["ForeignCurrencyTransactionGainLossBeforeTax"],
    "ebt": [
        "IncomeLossFromContinuingOperationsBeforeIncomeTaxesExtraordinaryItemsNoncontrollingInterest",
        "IncomeLossFromContinuingOperationsBeforeIncomeTaxesMinorityInterestAndIncomeLossFromEquityMethodInvestments",
    ],
    "income_tax": ["IncomeTaxExpenseBenefit"],
    "net_income": [
        "NetIncomeLoss",
        "NetIncomeLossAvailableToCommonStockholdersBasic",
    ],
    "eps_diluted": ["EarningsPerShareDiluted"],
    "eps_basic": ["EarningsPerShareBasic"],
    "shares_diluted": ["WeightedAverageNumberOfDilutedSharesOutstanding"],
    "shares_basic": ["WeightedAverageNumberOfSharesOutstandingBasic"],
    "dps": [
        "CommonStockDividendsPerShareDeclared",
        "CommonStockDividendsPerShareCashPaid",
    ],
    "ebitda": [],  # calculado: operating_income + da
    # Supplementary IS
    "da": [
        "DepreciationAndAmortization",
        "DepreciationDepletionAndAmortization",
        "Depreciation",
    ],
    "da_goodwill": ["AmortizationOfIntangibleAssets"],
    "sbc": ["ShareBasedCompensation"],
    "effective_tax_rate": ["EffectiveIncomeTaxRateContinuingOperations"],
    "rd_expense_detail": ["ResearchAndDevelopmentExpense"],
    "selling_marketing": ["SellingAndMarketingExpense"],
    "ga": ["GeneralAndAdministrativeExpense"],
    # ── Balance Sheet ─────────────────────────────────────────────────────
    "cash": ["CashAndCashEquivalentsAtCarryingValue", "Cash"],
    "short_term_investments": [
        "ShortTermInvestments",
        "AvailableForSaleSecuritiesCurrent",
        "MarketableSecuritiesCurrent",
    ],
    "accounts_receivable": [
        "AccountsReceivableNetCurrent",
        "ReceivablesNetCurrent",
    ],
    "inventory": ["InventoryNet", "InventoryFinishedGoodsNetOfReserves"],
    "deferred_tax_current": ["DeferredTaxAssetsCurrent"],
    "other_current_assets": ["OtherAssetsCurrent"],
    "total_current_assets": ["AssetsCurrent"],
    "gross_ppe": ["PropertyPlantAndEquipmentGross"],
    "accum_depreciation": [
        "AccumulatedDepreciationDepletionAndAmortizationPropertyPlantAndEquipment"
    ],
    "net_ppe": ["PropertyPlantAndEquipmentNet"],
    "long_term_investments": [
        "LongTermInvestments",
        "AvailableForSaleSecuritiesNoncurrent",
    ],
    "goodwill": ["Goodwill"],
    "intangibles": [
        "FiniteLivedIntangibleAssetsNet",
        "IntangibleAssetsNetExcludingGoodwill",
    ],
    "deferred_tax_lt": ["DeferredTaxAssetsLiabilitiesNet"],
    "other_lt_assets": ["OtherAssetsNoncurrent"],
    "total_assets": ["Assets"],
    "accounts_payable": ["AccountsPayableCurrent"],
    "accrued_expenses": [
        "AccruedLiabilitiesCurrent",
        "EmployeeRelatedLiabilitiesCurrent",
    ],
    "short_term_debt": [
        "ShortTermBorrowings",
        "CommercialPaper",
        "NotesPayableCurrent",
    ],
    "current_ltd": ["LongTermDebtCurrent", "CurrentPortionOfLongTermDebt"],
    "income_taxes_payable": ["IncomeTaxesPayable"],
    "deferred_revenue_current": ["DeferredRevenueCurrent", "ContractWithCustomerLiabilityCurrent"],
    "other_current_liabilities": ["OtherLiabilitiesCurrent"],
    "total_current_liabilities": ["LiabilitiesCurrent"],
    "long_term_debt": [
        "LongTermDebtNoncurrent",
        "LongTermDebt",
        "LongTermNotesPayable",
    ],
    "capital_leases": [
        "FinanceLeaseLiabilityNoncurrent",
        "CapitalLeaseObligationsNoncurrent",
    ],
    "deferred_revenue_noncurrent": [
        "DeferredRevenueNoncurrent",
        "ContractWithCustomerLiabilityNoncurrent",
    ],
    "deferred_tax_lt_liability": ["DeferredTaxLiabilitiesNoncurrent"],
    "other_lt_liabilities": ["OtherLiabilitiesNoncurrent"],
    "total_liabilities": ["Liabilities"],
    "common_stock_equity": ["CommonStockAndAdditionalPaidInCapital", "AdditionalPaidInCapital"],
    "retained_earnings": ["RetainedEarningsAccumulatedDeficit"],
    "aoci": ["AccumulatedOtherComprehensiveIncomeLossNetOfTax"],
    "total_equity": ["StockholdersEquity", "StockholdersEquityIncludingPortionAttributableToNoncontrollingInterest"],
    "total_liabilities_equity": ["LiabilitiesAndStockholdersEquity"],
    "shares_outstanding": ["CommonStockSharesOutstanding"],
    "employees": ["EntityNumberOfEmployees"],
    # ── Cash Flow ─────────────────────────────────────────────────────────
    "cfo": ["NetCashProvidedByUsedInOperatingActivities"],
    "capex": [
        "PaymentsToAcquirePropertyPlantAndEquipment",
        "PaymentsForCapitalImprovements",
    ],
    "acquisitions": ["PaymentsToAcquireBusinessesNetOfCashAcquired"],
    "investment_securities": [
        "PaymentsToAcquireInvestments",
        "PaymentsToAcquireAvailableForSaleSecurities",
    ],
    "other_investing": ["PaymentsForProceedsFromOtherInvestingActivities"],
    "cfi": ["NetCashProvidedByUsedInInvestingActivities"],
    "debt_issued": [
        "ProceedsFromIssuanceOfLongTermDebt",
        "ProceedsFromDebtNetOfIssuanceCosts",
    ],
    "debt_repaid": [
        "RepaymentsOfLongTermDebt",
        "RepaymentsOfDebt",
    ],
    "stock_issued": ["ProceedsFromIssuanceOfCommonStock"],
    "buybacks": ["PaymentsForRepurchaseOfCommonStock"],
    "dividends_paid": [
        "PaymentsOfDividendsCommonStock",
        "PaymentsOfDividends",
    ],
    "other_financing": ["ProceedsFromPaymentsForOtherFinancingActivities"],
    "cff": ["NetCashProvidedByUsedInFinancingActivities"],
    "fx_effect": ["EffectOfExchangeRateOnCashCashEquivalentsRestrictedCashAndRestrictedCashEquivalents"],
    "net_change_cash": [
        "CashCashEquivalentsRestrictedCashAndRestrictedCashEquivalentsPeriodIncreaseDecreaseIncludingExchangeRateEffect",
        "NetCashProvidedByUsedInContinuingOperations",
    ],
    "cash_interest_paid": ["InterestPaidNet"],
    "cash_taxes_paid": ["IncomeTaxesPaid"],
    "change_receivables": ["IncreaseDecreaseInAccountsReceivable"],
    "change_inventories": ["IncreaseDecreaseInInventories"],
    "change_payables": ["IncreaseDecreaseInAccountsPayable"],
    "change_deferred_revenue": ["IncreaseDecreaseInDeferredRevenue"],
    "change_income_taxes": ["IncreaseDecreaseInIncomeTaxesPayableNetOfIncomeTaxesReceivable"],
}


def _normalize_cik(cik: str) -> str:
    """Devuelve el CIK con ceros a la izquierda hasta 10 dígitos."""
    return cik.lstrip("0").zfill(10)


@lru_cache(maxsize=8)
def fetch_company_facts(cik: str) -> dict:
    """
    Descarga el JSON completo de hechos financieros de EDGAR para un CIK.
    Cachéado en memoria para evitar múltiples descargas.
    """
    cik_norm = _normalize_cik(cik)
    url = EDGAR_FACTS_URL.format(cik=f"CIK{cik_norm}")
    log.info("Descargando company facts de EDGAR: %s", url)
    resp = requests.get(url, headers=HEADERS, timeout=30)
    resp.raise_for_status()
    time.sleep(0.15)  # cortesía con el servidor de EDGAR
    return resp.json()


def _get_concept_values(
    facts: dict,
    concept: str,
    taxonomy: str = "us-gaap",
    unit: str = "USD",
) -> dict[str, float]:
    """
    Extrae los valores anuales (10-K) y trimestrales (10-Q) de un concepto XBRL.

    Devuelve un dict {periodo_YYYY-MM-DD: valor} donde el periodo es
    el campo 'end' del filing (fecha de fin del periodo reportado).
    Solo retorna valores de 'instant' o 'duration' de periodo completo (12m).
    """
    try:
        entries = facts["facts"][taxonomy][concept]["units"][unit]
    except KeyError:
        return {}

    annual: dict[str, float] = {}
    for entry in entries:
        form = entry.get("form", "")
        # 10-K: datos anuales completos
        if form in ("10-K", "10-K/A", "20-F"):
            end = entry.get("end", "")
            val = entry.get("val")
            if end and val is not None:
                annual[end] = float(val)
        # instant (balance sheet): mismo criterio
        elif form in ("10-K", "10-K/A") and "start" not in entry:
            end = entry.get("end", "")
            val = entry.get("val")
            if end and val is not None:
                annual[end] = float(val)
    return annual


def _get_concept_best(
    facts: dict,
    keys: list[str],
    end_date: str,
    tolerance_days: int = 45,
) -> Optional[float]:
    """
    Intenta cada concepto de la lista en orden y devuelve el primero con datos
    para el periodo indicado (±tolerance_days).
    """
    target = date.fromisoformat(end_date)
    for concept in keys:
        for taxonomy in ("us-gaap", "ifrs-full"):
            for unit in ("USD", "shares", "pure"):
                vals = _get_concept_values(facts, concept, taxonomy=taxonomy, unit=unit)
                if not vals:
                    continue
                # Buscar la fecha más cercana dentro de la tolerancia
                best_date = min(
                    vals.keys(),
                    key=lambda d: abs((date.fromisoformat(d) - target).days),
                )
                delta = abs((date.fromisoformat(best_date) - target).days)
                if delta <= tolerance_days:
                    return vals[best_date]
    return None


def extract_annual_financials(
    cik: str,
    fiscal_year_end_dates: list[str],
) -> dict[str, dict[str, Optional[float]]]:
    """
    Para cada fecha de cierre de año fiscal (YYYY-MM-DD), extrae todos los
    conceptos del XBRL_MAP.

    Devuelve:
    {
      "2025-06-30": {
        "revenues": 245122.0,
        "gross_profit": 171008.0,
        ...
        "ebitda": None,   # se calcula en post-proceso
      },
      ...
    }
    """
    facts = fetch_company_facts(cik)
    results: dict[str, dict[str, Optional[float]]] = {}

    for fy_end in fiscal_year_end_dates:
        log.debug("Extrayendo datos para periodo %s", fy_end)
        row: dict[str, Optional[float]] = {}

        for key, concepts in XBRL_MAP.items():
            if not concepts:
                row[key] = None  # se calcula después
                continue
            row[key] = _get_concept_best(facts, concepts, fy_end)

        # ── Cálculos derivados ────────────────────────────────────────────
        # EBITDA = Operating Income + D&A
        oi = row.get("operating_income")
        da = row.get("da")
        if oi is not None and da is not None:
            row["ebitda"] = oi + da
        else:
            row["ebitda"] = None

        # Free Cash Flow = CFO - CapEx
        cfo = row.get("cfo")
        capex = row.get("capex")
        if cfo is not None and capex is not None:
            row["fcf"] = cfo - abs(capex)
        else:
            row["fcf"] = None

        # Total Debt = Long-term Debt + Short-term Debt
        ltd = row.get("long_term_debt") or 0.0
        std = row.get("short_term_debt") or 0.0
        row["total_debt"] = ltd + std if (ltd or std) else None

        # Net Debt = Total Debt - Cash - Short-term Investments
        cash = row.get("cash") or 0.0
        sti = row.get("short_term_investments") or 0.0
        td = row.get("total_debt")
        row["net_debt"] = (td - cash - sti) if td is not None else None

        # Total Cash & ST Investments
        row["total_cash_sti"] = (cash + sti) if (cash or sti) else None

        # Tangible Book Value = Total Equity - Goodwill - Intangibles
        eq = row.get("total_equity")
        gw = row.get("goodwill") or 0.0
        ia = row.get("intangibles") or 0.0
        row["tangible_book_value"] = (eq - gw - ia) if eq is not None else None

        results[fy_end] = row

    return results


def get_fiscal_year_end_dates(
    fiscal_year_end_month: int,
    fiscal_years: list[int],
) -> list[str]:
    """
    Genera las fechas de cierre de año fiscal para la lista de años.
    Para MSFT (FY end junio): 2025 → "2025-06-30"
    Para AAPL (FY end septiembre): 2025 → "2025-09-30"
    """
    import calendar

    dates = []
    for fy in fiscal_years:
        last_day = calendar.monthrange(fy, fiscal_year_end_month)[1]
        dates.append(f"{fy}-{fiscal_year_end_month:02d}-{last_day:02d}")
    return dates
