"""
FGIP Narrative Generator - "Polite f*** you" language enforcement.

The 3-layer language system:
- Layer 1 (FACT): documented, recorded, disclosed, filings show
- Layer 2 (INFERENCE): suggests, consistent with, may indicate
- Layer 3 (HYPOTHESIS): working theory, to test, scenario (NEVER public)

Tone modes:
- FORENSIC: "According to [source]...", date + filing type, labeled assertions
- NEUTRAL: Plain language, still safe
- AGGRESSIVE: Private only, never public
"""

import re
from dataclasses import dataclass
from datetime import datetime
from typing import List, Dict, Optional, Tuple

from .models import (
    AssertionLevel,
    Visibility,
    Tone,
    EdgeStatus,
    CATNIP_PHRASES,
    SAFE_REPLACEMENTS,
    Edge,
    Source,
)


# Assertion level language templates
ASSERTION_PREFIXES = {
    AssertionLevel.FACT: {
        Tone.FORENSIC: [
            "According to {source}, ",
            "{source} documents that ",
            "As disclosed in {source}, ",
            "The record indicates ",
            "Filings show ",
        ],
        Tone.NEUTRAL: [
            "{source} shows ",
            "Records indicate ",
            "Documentation confirms ",
        ],
        Tone.AGGRESSIVE: [
            "The evidence proves ",
            "Documented proof: ",
        ],
    },
    AssertionLevel.INFERENCE: {
        Tone.FORENSIC: [
            "The pattern is consistent with ",
            "This suggests ",
            "The structure may indicate ",
            "We cannot rule out that ",
            "This warrants scrutiny: ",
        ],
        Tone.NEUTRAL: [
            "This appears to ",
            "Evidence suggests ",
            "The data is consistent with ",
        ],
        Tone.AGGRESSIVE: [
            "This clearly indicates ",
            "The implication is obvious: ",
        ],
    },
    AssertionLevel.HYPOTHESIS: {
        Tone.FORENSIC: [
            "Working hypothesis: ",
            "To test: ",
            "Scenario under evaluation: ",
            "We are evaluating whether ",
        ],
        Tone.NEUTRAL: [
            "Hypothesis: ",
            "Theory: ",
            "Possible explanation: ",
        ],
        Tone.AGGRESSIVE: [
            "Strong theory: ",
            "Likely explanation: ",
        ],
    },
}


@dataclass
class CatnipViolation:
    """A detected catnip phrase that invites lawsuits."""
    phrase: str
    position: int
    context: str
    suggested_replacement: str


@dataclass
class NarrativeResult:
    """Result of narrative generation."""
    text: str
    assertion_level: AssertionLevel
    tone: Tone
    sources_cited: List[str]
    catnip_violations: List[CatnipViolation]
    is_safe_for_public: bool


def detect_catnip(text: str) -> List[CatnipViolation]:
    """
    Detect catnip phrases that invite lawsuits.

    Returns list of violations with suggested replacements.
    """
    violations = []
    text_lower = text.lower()

    for phrase in CATNIP_PHRASES:
        # Find all occurrences
        pattern = r'\b' + re.escape(phrase) + r'\b'
        for match in re.finditer(pattern, text_lower, re.IGNORECASE):
            start = match.start()
            end = match.end()

            # Get context (50 chars before and after)
            ctx_start = max(0, start - 50)
            ctx_end = min(len(text), end + 50)
            context = text[ctx_start:ctx_end]

            violations.append(CatnipViolation(
                phrase=phrase,
                position=start,
                context=f"...{context}...",
                suggested_replacement=SAFE_REPLACEMENTS.get(phrase, f"[rephrase '{phrase}']"),
            ))

    return violations


def sanitize_text(text: str) -> Tuple[str, List[CatnipViolation]]:
    """
    Sanitize text by replacing catnip phrases with safe alternatives.

    Returns (sanitized_text, list of replacements made).
    """
    violations = detect_catnip(text)
    sanitized = text

    # Sort by position descending so replacements don't shift indices
    for violation in sorted(violations, key=lambda v: v.position, reverse=True):
        pattern = r'\b' + re.escape(violation.phrase) + r'\b'
        sanitized = re.sub(pattern, violation.suggested_replacement, sanitized, flags=re.IGNORECASE)

    return sanitized, violations


def generate_edge_narrative(
    edge: Edge,
    from_name: str,
    to_name: str,
    sources: List[Source],
    tone: Tone = Tone.FORENSIC,
    sanitize: bool = True,
) -> NarrativeResult:
    """
    Generate a narrative description of an edge relationship.

    Args:
        edge: The edge to describe
        from_name: Human-readable name of source node
        to_name: Human-readable name of target node
        sources: List of sources attached to this edge
        tone: Output tone (FORENSIC, NEUTRAL, AGGRESSIVE)
        sanitize: Whether to auto-replace catnip phrases

    Returns:
        NarrativeResult with text and metadata
    """
    assertion_level = edge.assertion_level

    # Select prefix based on assertion level and tone
    prefixes = ASSERTION_PREFIXES.get(assertion_level, {}).get(tone, [])
    if not prefixes:
        prefix = ""
    else:
        # Use first source for citation if available
        if sources and sources[0].title:
            source_cite = f"{sources[0].title}"
            if sources[0].date_published:
                source_cite += f" ({sources[0].date_published.strftime('%Y-%m-%d')})"
        elif sources and sources[0].url:
            source_cite = sources[0].url
        else:
            source_cite = "available records"

        prefix = prefixes[0].format(source=source_cite)

    # Build core statement
    edge_type_readable = edge.edge_type.replace("_", " ").lower()
    core = f"{from_name} {edge_type_readable} {to_name}"

    # Add date range if present
    date_suffix = ""
    if edge.date_occurred_start:
        date_suffix = f" (from {edge.date_occurred_start.strftime('%Y-%m-%d')}"
        if edge.date_occurred_end:
            date_suffix += f" to {edge.date_occurred_end.strftime('%Y-%m-%d')}"
        date_suffix += ")"

    # Build full text
    full_text = f"{prefix}{core}{date_suffix}."

    # Add assertion level label for forensic tone
    if tone == Tone.FORENSIC:
        full_text += f" [{assertion_level.value}]"

    # Add evidence status
    if edge.status == EdgeStatus.EVIDENCED:
        full_text += " [EVIDENCED]"
    elif edge.status == EdgeStatus.CLAIMED:
        full_text += " [CLAIMED - pending verification]"
    elif edge.status == EdgeStatus.DISPUTED:
        full_text += " [DISPUTED]"

    # Detect catnip
    violations = detect_catnip(full_text)

    # Sanitize if requested
    if sanitize and violations:
        full_text, _ = sanitize_text(full_text)

    # Determine if safe for public
    is_safe = (
        assertion_level != AssertionLevel.HYPOTHESIS
        and tone != Tone.AGGRESSIVE
        and edge.visibility == Visibility.PUBLIC
        and edge.status == EdgeStatus.EVIDENCED
        and len(violations) == 0
    )

    return NarrativeResult(
        text=full_text,
        assertion_level=assertion_level,
        tone=tone,
        sources_cited=[s.source_id for s in sources],
        catnip_violations=violations,
        is_safe_for_public=is_safe,
    )


def generate_path_narrative(
    path_nodes: List[str],
    path_edges: List[Edge],
    node_names: Dict[str, str],
    sources_by_edge: Dict[str, List[Source]],
    tone: Tone = Tone.FORENSIC,
) -> NarrativeResult:
    """
    Generate a narrative for a causal path.

    Args:
        path_nodes: List of node IDs in path order
        path_edges: List of edges in path order
        node_names: Dict mapping node_id -> human name
        sources_by_edge: Dict mapping edge_id -> list of sources
        tone: Output tone

    Returns:
        NarrativeResult describing the causal chain
    """
    if len(path_edges) == 0:
        return NarrativeResult(
            text="No causal path found.",
            assertion_level=AssertionLevel.FACT,
            tone=tone,
            sources_cited=[],
            catnip_violations=[],
            is_safe_for_public=True,
        )

    segments = []
    all_sources = []
    all_violations = []
    min_assertion = AssertionLevel.FACT
    all_evidenced = True

    for i, edge in enumerate(path_edges):
        from_name = node_names.get(edge.from_node_id, edge.from_node_id)
        to_name = node_names.get(edge.to_node_id, edge.to_node_id)
        sources = sources_by_edge.get(edge.edge_id, [])

        result = generate_edge_narrative(edge, from_name, to_name, sources, tone)
        segments.append(result.text)
        all_sources.extend(result.sources_cited)
        all_violations.extend(result.catnip_violations)

        # Track weakest assertion level
        if edge.assertion_level == AssertionLevel.HYPOTHESIS:
            min_assertion = AssertionLevel.HYPOTHESIS
        elif edge.assertion_level == AssertionLevel.INFERENCE and min_assertion != AssertionLevel.HYPOTHESIS:
            min_assertion = AssertionLevel.INFERENCE

        if edge.status != EdgeStatus.EVIDENCED:
            all_evidenced = False

    # Combine segments
    full_text = " → ".join(segments)

    # Add summary
    full_text += f"\n\nPath summary: {len(path_edges)} steps, "
    full_text += f"assertion level: {min_assertion.value}, "
    full_text += f"fully evidenced: {all_evidenced}"

    is_safe = (
        min_assertion != AssertionLevel.HYPOTHESIS
        and tone != Tone.AGGRESSIVE
        and all_evidenced
        and len(all_violations) == 0
    )

    return NarrativeResult(
        text=full_text,
        assertion_level=min_assertion,
        tone=tone,
        sources_cited=list(set(all_sources)),
        catnip_violations=all_violations,
        is_safe_for_public=is_safe,
    )


def generate_disclaimer() -> str:
    """Generate the standard disclaimer block."""
    return """DISCLAIMER: This system is a research database. It records links between entities
only when supported by cited sources. Some entries are flagged as hypotheses pending
verification. Nothing here is investment advice. Interpretations are provisional and
subject to revision as new primary documents emerge."""


def lint_notes(notes: str) -> Tuple[bool, List[CatnipViolation]]:
    """
    Lint notes field for catnip phrases.

    Returns (is_clean, violations)
    """
    violations = detect_catnip(notes)
    return len(violations) == 0, violations
