"""Claim extraction module for battle card generation.

Parses narrative documents and data modules to extract
claim/evidence/implication triples suitable for FIA card assembly.
"""

from __future__ import annotations

import importlib
import importlib.util
import json
import re
from pathlib import Path
from typing import Any, Optional


class ClaimExtractor:
    """Extract quantified claims from narratives and data modules.

    Methods
    -------
    parse_narrative(narrative_path: str) -> list[dict]
        Parse a narrative Markdown file for claim triples.
    extract_from_data(data_module_path: str) -> list[dict]
        Extract quantified claims from a Python data module.
    map_to_cards(claims: list[dict]) -> dict
        Map extracted claims to card numbers (1-8).
    export_cards(cards_path: str) -> dict
        Read claims.json and return structured card data.

    Claim dict format
    -----------------
    {
        "card_number": int,
        "section": "fact" | "impact" | "act",
        "claim": str,
        "evidence": str,
        "source": str,
        "confidence": str,  # optional
    }
    """

    # Card number to topic mapping for heuristic assignment
    _CARD_TOPICS: dict[int, tuple[str, ...]] = {
        1: ("valuation", "cape", "market cap", "shiller", "p/e", "dividend"),
        2: ("infrastructure", "data center", "hyperscaler", "capex", "nvidia"),
        3: ("gpu", "utilization", "tensor", "compute", "idle"),
        4: ("startup", "funding", "venture", "openai", "anthropic", "mistral"),
        5: ("enterprise", "deployment", "klarna", "jpmorgan", "servicenow", "production"),
        6: ("developer", "coding", "programming", "ide", "copilot", "github"),
        7: ("quality", "security", "vulnerability", "bug", "dora"),
        8: (
            "productivity",
            "long-term",
            "trajectory",
            "efficiency",
            "accenture",
            "microsoft research",
        ),
    }

    def parse_narrative(self, narrative_path: str) -> list[dict]:
        """Parse a Markdown narrative for claim/evidence/implication triples.

        Reads the narrative file and extracts bullet points and key
        statements that contain quantitative data, classifying each
        into fact, impact, or act sections and mapping to card numbers.

        Parameters
        ----------
        narrative_path : str
            Path to the Markdown narrative file.

        Returns
        -------
        list[dict]
            List of extracted claim dicts.
        """
        claims: list[dict] = []
        path = Path(narrative_path)

        if not path.exists():
            return claims

        text = path.read_text(encoding="utf-8")

        # Pattern: bullet points that contain quantitative data
        # Matches lines starting with "- " that contain numbers
        bullet_pattern = re.compile(
            r"^[-*]\s+(.+?[\d%]+\S.*?)$",
            re.MULTILINE,
        )

        for match in bullet_pattern.finditer(text):
            bullet_text = match.group(1).strip()

            # Extract evidence (numeric data points)
            numbers = re.findall(
                r"\d+(?:,\d{3})*(?:\.\d+)?[%$]?",
                bullet_text,
            )
            evidence = ", ".join(numbers) if numbers else "qualitative"

            # Determine section by context keywords
            section = self._classify_section(bullet_text)

            # Map to card number by topic
            card_number = self._match_topic(bullet_text)

            claim = {
                "card_number": card_number,
                "section": section,
                "claim": bullet_text,
                "evidence": evidence,
                "source": "case_narrative",
            }
            claims.append(claim)

        return claims

    def extract_from_data(self, data_module_path: str) -> list[dict]:
        """Extract quantified claims from a Python data module.

        Reads module-level list[dict] or dict constants and
        extracts notable data points as claims.

        Parameters
        ----------
        data_module_path : str
            Path to the Python data module file.

        Returns
        -------
        list[dict]
            List of extracted claim dicts.
        """
        claims: list[dict] = []
        path = Path(data_module_path)

        if not path.exists():
            return claims

        text = path.read_text(encoding="utf-8")

        # Extract module-level variable names (list[dict] or dict)
        var_pattern = re.compile(
            r"^(\w+):\s*list\[dict\].*?=(\[.*?\])",
            re.MULTILINE | re.DOTALL,
        )

        module_name = path.stem

        for match in var_pattern.finditer(text):
            var_name = match.group(1)
            data_str = match.group(2)

            # Extract representative values from the data
            numbers = re.findall(
                r"[\d]+(?:,[\d]{3})*(?:\.[\d]+)?",
                data_str,
            )

            if numbers:
                # Take first and last significant values
                sample = f"Range: {numbers[0]} to {numbers[-1]}"
                card_number = self._match_topic(var_name)

                claim = {
                    "card_number": card_number,
                    "section": "fact",
                    "claim": f"{var_name}: {sample}",
                    "evidence": sample,
                    "source": module_name,
                }
                claims.append(claim)

        return claims

    def extract_from_data_modules(
        self,
        market_bubbles_module: Optional[str] = None,
        ai_infra_module: Optional[str] = None,
        agent_adoption_module: Optional[str] = None,
        productivity_module: Optional[str] = None,
    ) -> list[dict]:
        """Extract cross-referenced data points from data modules.

        Dynamically imports the specified data modules and extracts
        key numeric values as claims with proper source attribution.

        Parameters
        ----------
        market_bubbles_module : str, optional
            Module path for market_bubbles data.
        ai_infra_module : str, optional
            Module path for ai_infrastructure data.
        agent_adoption_module : str, optional
            Module path for agent_adoption data.
        productivity_module : str, optional
            Module path for productivity data.

        Returns
        -------
        list[dict]
            List of cross-referenced claim dicts from data modules.
        """
        claims: list[dict] = []

        # Market bubbles data -> Card 1
        if market_bubbles_module:
            mod = self._import_module(market_bubbles_module)
            if mod:
                claims.extend(self._extract_market_bubble_claims(mod))

        # AI infrastructure data -> Cards 2, 3
        if ai_infra_module:
            mod = self._import_module(ai_infra_module)
            if mod:
                claims.extend(self._extract_infrastructure_claims(mod))

        # Agent adoption data -> Cards 5, 6, 7
        if agent_adoption_module:
            mod = self._import_module(agent_adoption_module)
            if mod:
                claims.extend(self._extract_adoption_claims(mod))

        # Productivity data -> Cards 5, 8
        if productivity_module:
            mod = self._import_module(productivity_module)
            if mod:
                claims.extend(self._extract_productivity_claims(mod))

        return claims

    def map_to_cards(self, claims: list[dict]) -> dict:
        """Map a list of claims to card numbers (1-8).

        Parameters
        ----------
        claims : list[dict]
            List of claim dicts to organize.

        Returns
        -------
        dict
            Mapping of card_number -> list of claims for that card.
        """
        card_map: dict[int, list[dict]] = {i: [] for i in range(1, 9)}

        for claim in claims:
            card_num = claim.get("card_number", 1)
            # Clamp to valid range
            card_num = max(1, min(8, card_num))
            card_map[card_num].append(claim)

        return card_map

    def export_cards(self, cards_path: str) -> dict:
        """Read claims.json and return structured card data.

        Parameters
        ----------
        cards_path : str
            Path to the claims.json file.

        Returns
        -------
        dict
            Parsed card data with metadata.
        """
        path = Path(cards_path)

        if not path.exists():
            return {}

        with open(path, "r", encoding="utf-8") as f:
            return json.load(f)

    def count_claims(self, cards_data: dict) -> dict:
        """Count claims per card and section.

        Parameters
        ----------
        cards_data : dict
            Parsed cards data from claims.json.

        Returns
        -------
        dict
            Summary counts per card and overall.
        """
        summary: dict[str, Any] = {}
        cards = cards_data.get("cards", {})

        for card_id, card in cards.items():
            fact_count = len(card.get("fact", []))
            impact_count = len(card.get("impact", []))
            act_count = len(card.get("act", []))
            total = fact_count + impact_count + act_count

            summary[f"card_{card_id}"] = {
                "title": card.get("title", f"Card {card_id}"),
                "fact": fact_count,
                "impact": impact_count,
                "act": act_count,
                "total": total,
            }

        summary["total_cards"] = len(cards)
        summary["total_claims"] = sum(
            v["total"] for v in summary.values() if isinstance(v, dict) and "total" in v
        )

        return summary

    # -----------------------------------------------------------------------
    # Data module extraction helpers
    # -----------------------------------------------------------------------

    def _import_module(self, module_path: str) -> Optional[Any]:
        """Dynamically import a Python module from a file path."""
        try:
            path = Path(module_path)
            if not path.exists():
                return None

            spec = importlib.util.spec_from_file_location(path.stem, str(path))
            if spec is None or spec.loader is None:
                return None

            mod = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(mod)
            return mod
        except Exception:
            return None

    def _extract_market_bubble_claims(self, mod: Any) -> list[dict]:
        """Extract claims from market_bubbles data module."""
        claims: list[dict] = []

        # Shiller CAPE
        cape_data = getattr(mod, "shiller_cape", None)
        cape_meta = getattr(mod, "shiller_cape_meta", {})
        if cape_data and isinstance(cape_data, list):
            latest = cape_data[-1] if cape_data else {}
            mean_val = cape_meta.get("historical_mean", "N/A")
            peak_val = max(d.get("value", 0) for d in cape_data) if cape_data else 0
            peak_year = next(
                (d.get("year", "?") for d in cape_data if d.get("value") == peak_val),
                "?",
            )
            claims.append(
                {
                    "card_number": 1,
                    "section": "fact",
                    "claim": f"Shiller CAPE current: {latest.get('value', 'N/A')}, "
                    f"historical mean: {mean_val}, peak: {peak_val} "
                    f"(year {peak_year})",
                    "evidence": f"CAPE {latest.get('year', '?')}: "
                    f"{latest.get('value', 'N/A')}",
                    "source": "market_bubbles.shiller_cape",
                    "confidence": cape_meta.get("confidence", "HIGH"),
                }
            )

        # Buffett Indicator
        buffett_data = getattr(mod, "buffett_indicator", None)
        buffett_meta = getattr(mod, "buffett_indicator_meta", {})
        if buffett_data and isinstance(buffett_data, list):
            latest = buffett_data[-1] if buffett_data else {}
            claims.append(
                {
                    "card_number": 1,
                    "section": "fact",
                    "claim": f"Buffett Indicator: {latest.get('value', 'N/A')}% "
                    f"(200% danger threshold)",
                    "evidence": f"{latest.get('year', '?')}: "
                    f"{latest.get('value', 'N/A')}%",
                    "source": "market_bubbles.buffett_indicator",
                    "confidence": buffett_meta.get("confidence", "MEDIUM-HIGH"),
                }
            )

        # S&P 500 P/E
        pe_data = getattr(mod, "sp500_pe", None)
        pe_meta = getattr(mod, "sp500_pe_meta", {})
        if pe_data and isinstance(pe_data, list):
            latest = pe_data[-1] if pe_data else {}
            mean_val = pe_meta.get("historical_mean", "N/A")
            claims.append(
                {
                    "card_number": 1,
                    "section": "fact",
                    "claim": f"S&P 500 P/E: {latest.get('value', 'N/A')} "
                    f"(mean: {mean_val})",
                    "evidence": f"{latest.get('year', '?')}: "
                    f"{latest.get('value', 'N/A')}",
                    "source": "market_bubbles.sp500_pe",
                    "confidence": pe_meta.get("confidence", "HIGH"),
                }
            )

        # Dividend Yield
        div_data = getattr(mod, "sp500_dividend_yield", None)
        div_meta = getattr(mod, "sp500_dividend_yield_meta", {})
        if div_data and isinstance(div_data, list):
            latest = div_data[-1] if div_data else {}
            mean_val = div_meta.get("historical_mean", "N/A")
            claims.append(
                {
                    "card_number": 1,
                    "section": "fact",
                    "claim": f"S&P 500 dividend yield: "
                    f"{latest.get('value', 'N/A')}% (mean: {mean_val}%)",
                    "evidence": f"{latest.get('year', '?')}: "
                    f"{latest.get('value', 'N/A')}%",
                    "source": "market_bubbles.sp500_dividend_yield",
                    "confidence": div_meta.get("confidence", "HIGH"),
                }
            )

        # Debt ratios
        debt_data = getattr(mod, "us_debt_ratios", None)
        if debt_data and isinstance(debt_data, list):
            latest = debt_data[-1] if debt_data else {}
            claims.append(
                {
                    "card_number": 1,
                    "section": "fact",
                    "claim": f"Federal debt/GDP: "
                    f"{latest.get('federal_debt_gdp_percent', 'N/A')}% "
                    f"(household: "
                    f"{latest.get('household_debt_gdp_percent', 'N/A')}%)",
                    "evidence": f"{latest.get('year', '?')}: federal "
                    f"{latest.get('federal_debt_gdp_percent', 'N/A')}%, "
                    f"household "
                    f"{latest.get('household_debt_gdp_percent', 'N/A')}%",
                    "source": "market_bubbles.us_debt_ratios",
                    "confidence": "HIGH",
                }
            )

        return claims

    def _extract_infrastructure_claims(self, mod: Any) -> list[dict]:
        """Extract claims from ai_infrastructure data module."""
        claims: list[dict] = []

        # Hyperscaler capex
        capex_data = getattr(mod, "hyperscaler_capex_annual", None)
        if capex_data and isinstance(capex_data, list):
            # Sum 2020 and 2026 totals
            years = {}
            for entry in capex_data:
                year = entry.get("year")
                if year not in years:
                    years[year] = 0.0
                years[year] += entry.get("capex_billions", 0)

            y2020 = years.get(2020, 0)
            y2026 = years.get(2026, 0)
            claims.append(
                {
                    "card_number": 2,
                    "section": "fact",
                    "claim": f"Hyperscaler combined capex: "
                    f"${y2020:.1f}B (2020) -> "
                    f"${y2026:.0f}B (2026 projected)",
                    "evidence": f"2020: ${y2020:.1f}B, 2026: ${y2026:.0f}B",
                    "source": "ai_infrastructure.hyperscaler_capex_annual",
                    "confidence": "HIGH",
                }
            )

        # AI capex share
        ai_share = getattr(mod, "hyperscaler_ai_capex_share", None)
        if ai_share and isinstance(ai_share, dict):
            latest_year = max(ai_share.keys())
            share = ai_share[latest_year]
            claims.append(
                {
                    "card_number": 2,
                    "section": "fact",
                    "claim": f"AI capex share: "
                    f"{share.get('low', 'N/A')}-{share.get('high', 'N/A')}% "
                    f"of hyperscaler spending in {latest_year}",
                    "evidence": f"{share.get('low')}% to "
                    f"{share.get('high')}%",
                    "source": "ai_infrastructure.hyperscaler_ai_capex_share",
                    "confidence": "MEDIUM",
                }
            )

        # NVIDIA revenue
        nvidia_data = getattr(mod, "nvidia_revenue", None)
        if nvidia_data and isinstance(nvidia_data, list):
            first_entry = nvidia_data[0] if nvidia_data else {}
            last_entry = nvidia_data[-1] if nvidia_data else {}

            # Get data center or compute revenue
            first_dc = first_entry.get(
                "data_center_billions",
                first_entry.get("compute_billions", 0),
            )
            last_dc = last_entry.get(
                "data_center_billions",
                last_entry.get("compute_billions", 0),
            )

            claims.append(
                {
                    "card_number": 2,
                    "section": "fact",
                    "claim": f"NVIDIA data center revenue: "
                    f"${first_dc:.2f}B ({first_entry.get('fiscal_quarter', '?')}) "
                    f"-> ${last_dc:.1f}B "
                    f"({last_entry.get('fiscal_quarter', '?')})",
                    "evidence": f"{first_entry.get('fiscal_quarter', '?')}: "
                    f"${first_dc:.2f}B, "
                    f"{last_entry.get('fiscal_quarter', '?')}: "
                    f"${last_dc:.1f}B",
                    "source": "ai_infrastructure.nvidia_revenue",
                    "confidence": "HIGH",
                }
            )

        # Tech layoffs
        layoffs = getattr(mod, "tech_layoffs", None)
        layoffs_meta = getattr(mod, "layoffs_meta", {})
        if layoffs and isinstance(layoffs, list):
            total_cut = layoffs_meta.get("total_jobs_cut_cumulative", 0)
            peak_year = layoffs_meta.get("peak_year", "?")
            peak_cut = layoffs_meta.get("peak_jobs_cut", 0)
            claims.append(
                {
                    "card_number": 2,
                    "section": "fact",
                    "claim": f"Tech layoffs: {total_cut:,} cumulative "
                    f"(peak: {peak_cut:,} in {peak_year})",
                    "evidence": f"Peak {peak_year}: {peak_cut:,} jobs",
                    "source": "ai_infrastructure.tech_layoffs",
                    "confidence": "HIGH",
                }
            )

        return claims

    def _extract_adoption_claims(self, mod: Any) -> list[dict]:
        """Extract claims from agent_adoption data module."""
        claims: list[dict] = []

        # Developer AI adoption
        dev_data = getattr(mod, "developer_ai_adoption", None)
        if dev_data and isinstance(dev_data, list):
            for entry in dev_data:
                metric = entry.get("metric", "")
                value = entry.get("value", 0)
                source = entry.get("source", "")

                # Card 6: Developer adoption
                if any(
                    kw in metric
                    for kw in ["copilot", "daily", "use_or_plan", "regular_ai"]
                ):
                    claims.append(
                        {
                            "card_number": 6,
                            "section": "fact",
                            "claim": f"{source}: {metric} = {value}",
                            "evidence": str(value),
                            "source": f"agent_adoption.developer_ai_adoption",
                            "confidence": "HIGH",
                        }
                    )

        # Agent survey data
        survey_data = getattr(mod, "agent_survey_data", None)
        if survey_data and isinstance(survey_data, dict):
            for survey_name, metrics in survey_data.items():
                prod_rate = metrics.get("production", None)
                if prod_rate is not None:
                    claims.append(
                        {
                            "card_number": 5,
                            "section": "fact",
                            "claim": f"{survey_name}: "
                            f"{prod_rate}% deploying agents in production",
                            "evidence": f"{prod_rate}%",
                            "source": f"agent_adoption.agent_survey_data.{survey_name}",
                            "confidence": "HIGH",
                        }
                    )

        # Code quality issues
        quality_data = getattr(mod, "code_quality_in_production", None)
        if quality_data and isinstance(quality_data, list):
            for entry in quality_data:
                finding = entry.get("finding", "")
                confidence = entry.get("confidence", "MEDIUM")
                claims.append(
                    {
                        "card_number": 7,
                        "section": "fact",
                        "claim": finding,
                        "evidence": entry.get("source", "N/A"),
                        "source": "agent_adoption.code_quality_in_production",
                        "confidence": confidence,
                    }
                )

        # Failure modes
        failure_data = getattr(mod, "failure_modes", None)
        if failure_data and isinstance(failure_data, list):
            for entry in failure_data:
                category = entry.get("category", "")
                rate = entry.get("rate_percent", None)
                source = entry.get("source", "")

                if rate is not None:
                    claims.append(
                        {
                            "card_number": 8,
                            "section": "fact",
                            "claim": f"{source}: {category} - "
                            f"{rate}% failure/abandonment rate",
                            "evidence": f"{rate}%",
                            "source": f"agent_adoption.failure_modes",
                            "confidence": entry.get("confidence", "MEDIUM"),
                        }
                    )

        return claims

    def _extract_productivity_claims(self, mod: Any) -> list[dict]:
        """Extract claims from productivity data module."""
        claims: list[dict] = []

        # Case studies
        case_data = getattr(mod, "case_studies", None)
        if case_data and isinstance(case_data, list):
            for case in case_data:
                company = case.get("company", "Unknown")
                confidence = case.get("confidence", "MEDIUM")
                metrics = case.get("metrics", {})

                # Build metric summary
                metric_parts = []
                for k, v in metrics.items():
                    if isinstance(v, (int, float)):
                        metric_parts.append(f"{k}: {v:,}")
                    elif isinstance(v, str):
                        metric_parts.append(f"{k}: {v}")

                metric_str = "; ".join(metric_parts[:5]) if metric_parts else "N/A"

                claims.append(
                    {
                        "card_number": 5,
                        "section": "fact",
                        "claim": f"{company}: {metric_str}",
                        "evidence": metric_str,
                        "source": f"productivity.case_studies ({company})",
                        "confidence": confidence,
                    }
                )

        # Failure modes
        failure_data = getattr(mod, "failure_modes", None)
        if failure_data and isinstance(failure_data, list):
            for entry in failure_data:
                category = entry.get("category", "")
                source = entry.get("source", "")
                rate = entry.get("rate_percent", None)

                if rate is not None:
                    claims.append(
                        {
                            "card_number": 8,
                            "section": "fact",
                            "claim": f"{source}: {category} - {rate}%",
                            "evidence": entry.get("detail", str(rate)),
                            "source": f"productivity.failure_modes",
                            "confidence": entry.get("confidence", "MEDIUM"),
                        }
                    )

        return claims

    # -----------------------------------------------------------------------
    # Private helpers
    # -----------------------------------------------------------------------

    @staticmethod
    def _classify_section(text: str) -> str:
        """Classify a text snippet into fact, impact, or act section."""
        lower = text.lower()
        if any(
            kw in lower
            for kw in [
                "risk",
                "impact",
                "threat",
                "consequence",
                "could",
                "would",
                "may lead",
                "potential",
            ]
        ):
            return "impact"
        if any(
            kw in lower
            for kw in [
                "should",
                "recommend",
                "act",
                "take action",
                "consider",
                "monitor",
                "hedge",
            ]
        ):
            return "act"
        return "fact"

    @staticmethod
    def _match_topic(text: str) -> int:
        """Match text to the closest card number by topic keywords."""
        lower = text.lower()
        for card_num, keywords in ClaimExtractor._CARD_TOPICS.items():
            if any(kw in lower for kw in keywords):
                return card_num
        return 1  # Default to card 1
