"""
FGIP Data Models - Dataclasses for nodes, edges, sources, and claims.
"""

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional, List, Dict, Any


class EdgeStatus(Enum):
    """Status of an edge in the causality graph."""
    CLAIMED = "CLAIMED"        # Initial state, no evidence attached
    EVIDENCED = "EVIDENCED"    # Source artifact attached with SHA256
    DISPUTED = "DISPUTED"      # Contradicting evidence found
    RETRACTED = "RETRACTED"    # Explicitly withdrawn


class Sensitivity(Enum):
    """Sensitivity level for edges - controls export visibility."""
    LOW = "LOW"     # Public export allowed
    MED = "MED"     # Export with source requirement
    HIGH = "HIGH"   # Requires Tier 0/1 sources for public export


class AssertionLevel(Enum):
    """
    Assertion level for edges - the 3-layer language system.

    FACT: Proven with receipts. Use: documented, recorded, disclosed, filings show
    INFERENCE: Plausible but not nailed. Use: suggests, consistent with, may indicate
    HYPOTHESIS: Spicy, private, experimental. Use: working theory, to test, scenario
    """
    FACT = "FACT"              # Proven - receipts exist
    INFERENCE = "INFERENCE"    # Reasonable conclusion from facts
    HYPOTHESIS = "HYPOTHESIS"  # Testable idea - NEVER export to public


class Visibility(Enum):
    """Visibility level for export gating."""
    PRIVATE = "PRIVATE"        # Internal only
    PUBLIC = "PUBLIC"          # Can be exported publicly (if assertion level permits)


class Tone(Enum):
    """Output tone for narrative generation."""
    FORENSIC = "FORENSIC"      # Default: "According to [source]...", date + filing type
    NEUTRAL = "NEUTRAL"        # Plain language, still safe
    AGGRESSIVE = "AGGRESSIVE"  # Private only, never public


# Catnip phrases that invite lawsuits - AVOID
CATNIP_PHRASES = [
    "controls",
    "coordinating",
    "corrupt",
    "captured",
    "illegal",
    "colluding",
    "conspiracy",
    "fraud",
    "criminal",
    "bribed",
    "rigged",
]

# Safe replacements for catnip phrases
SAFE_REPLACEMENTS = {
    "controls": "has measurable influence via",
    "coordinating": "structure creates aligned incentives",
    "corrupt": "funding model creates conflicts",
    "captured": "governance design can produce outcomes consistent with",
    "illegal": "not established in public record as lawful; hypothesis pending court ruling",
    "colluding": "incentive alignment suggests",
    "conspiracy": "pattern is consistent with coordinated incentives",
    "fraud": "discrepancies warrant scrutiny",
    "criminal": "subject to ongoing investigation per",
    "bribed": "received payments from",
    "rigged": "structure creates predictable outcomes",
}


class SourceType(Enum):
    """Type of source artifact."""
    DOCUMENT = "DOCUMENT"        # PDF, paper, report
    WEBSITE = "WEBSITE"          # URL source
    DATABASE = "DATABASE"        # Structured data source
    INTERVIEW = "INTERVIEW"      # Primary interview
    FILING = "FILING"            # SEC, court, regulatory filing
    NEWS = "NEWS"                # News article
    ARCHIVE = "ARCHIVE"          # Historical archive
    OTHER = "OTHER"


class ClaimType(Enum):
    """Type of claim being tracked."""
    OWNERSHIP = "OWNERSHIP"      # Ownership/control claims
    FUNDING = "FUNDING"          # Funding/investment claims
    INFLUENCE = "INFLUENCE"      # Political/social influence
    EMPLOYMENT = "EMPLOYMENT"    # Employment relationship
    MEMBERSHIP = "MEMBERSHIP"    # Organization membership
    OTHER = "OTHER"


@dataclass
class Node:
    """A node in the causality graph (entity)."""
    node_id: str                           # e.g., "ORG:us_chamber"
    node_type: str                         # e.g., "ORGANIZATION", "PERSON", "LEGISLATION"
    name: str                              # Human-readable name
    normalized_name: Optional[str] = None  # Lowercase, no punctuation
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None
    metadata: Dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if self.normalized_name is None:
            self.normalized_name = self._normalize(self.name)
        if self.created_at is None:
            self.created_at = datetime.utcnow()
        if self.updated_at is None:
            self.updated_at = self.created_at

    @staticmethod
    def _normalize(name: str) -> str:
        """Normalize name for matching."""
        import re
        return re.sub(r'[^a-z0-9]', '', name.lower())


@dataclass
class EdgeAttr:
    """Key-value attribute on an edge."""
    edge_id: str
    key: str
    value: str
    value_type: str = "string"  # string, int, float, json


@dataclass
class Edge:
    """A directed edge in the causality graph (relationship)."""
    edge_id: str
    edge_type: str                          # e.g., "LOBBIED", "FUNDED", "OWNS_SHARES"
    from_node_id: str
    to_node_id: str
    status: EdgeStatus = EdgeStatus.CLAIMED
    sensitivity: Sensitivity = Sensitivity.LOW
    assertion_level: 'AssertionLevel' = None  # FACT, INFERENCE, HYPOTHESIS
    visibility: 'Visibility' = None           # PRIVATE, PUBLIC
    confidence: Optional[float] = None      # 0.0 - 1.0
    date_occurred_start: Optional[datetime] = None
    date_occurred_end: Optional[datetime] = None
    notes: Optional[str] = None
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None
    attrs: List[EdgeAttr] = field(default_factory=list)
    source_ids: List[str] = field(default_factory=list)

    def __post_init__(self):
        if self.created_at is None:
            self.created_at = datetime.utcnow()
        if self.updated_at is None:
            self.updated_at = self.created_at
        # Default assertion level based on status
        if self.assertion_level is None:
            from .models import AssertionLevel
            self.assertion_level = AssertionLevel.INFERENCE
        # Default visibility
        if self.visibility is None:
            from .models import Visibility
            self.visibility = Visibility.PRIVATE

    def can_export_public(self) -> bool:
        """Check if this edge can be exported publicly."""
        from .models import AssertionLevel, Visibility
        # HYPOTHESIS can never be public
        if self.assertion_level == AssertionLevel.HYPOTHESIS:
            return False
        # Must be marked for public visibility
        if self.visibility != Visibility.PUBLIC:
            return False
        # FACT or INFERENCE with EVIDENCED status is OK
        if self.assertion_level == AssertionLevel.FACT:
            return self.status == EdgeStatus.EVIDENCED
        if self.assertion_level == AssertionLevel.INFERENCE:
            return self.status == EdgeStatus.EVIDENCED
        return False


@dataclass
class Source:
    """A source artifact providing evidence for an edge."""
    source_id: str
    source_type: SourceType = SourceType.OTHER
    title: Optional[str] = None
    author: Optional[str] = None
    publisher: Optional[str] = None
    date_published: Optional[datetime] = None
    date_accessed: Optional[datetime] = None
    url: Optional[str] = None
    file_path: Optional[str] = None          # Relative to sources_dir
    sha256: Optional[str] = None             # Hash of artifact content
    notes: Optional[str] = None
    created_at: Optional[datetime] = None
    tier: int = 2                            # 0=primary, 1=secondary, 2=tertiary

    def __post_init__(self):
        if self.created_at is None:
            self.created_at = datetime.utcnow()
        if self.date_accessed is None:
            self.date_accessed = datetime.utcnow()


@dataclass
class EdgeSource:
    """Link between an edge and a source with claim excerpt."""
    edge_id: str
    source_id: str
    claim_excerpt: Optional[str] = None      # Quote from source
    page_ref: Optional[str] = None           # Page number or section
    added_at: Optional[datetime] = None

    def __post_init__(self):
        if self.added_at is None:
            self.added_at = datetime.utcnow()


@dataclass
class Claim:
    """A trackable claim that may span multiple edges."""
    claim_id: str
    claim_text: str
    claim_type: ClaimType = ClaimType.OTHER
    required_tier: int = 2                   # Minimum source tier needed
    status: EdgeStatus = EdgeStatus.CLAIMED
    created_at: Optional[datetime] = None
    edge_ids: List[str] = field(default_factory=list)

    def __post_init__(self):
        if self.created_at is None:
            self.created_at = datetime.utcnow()


@dataclass
class ClaimEdge:
    """Link between a claim and an edge."""
    claim_id: str
    edge_id: str


@dataclass
class EdgeStatusLog:
    """Append-only log of edge status changes."""
    log_id: str
    edge_id: str
    old_status: EdgeStatus
    new_status: EdgeStatus
    reason: Optional[str] = None
    actor: Optional[str] = None              # Who made the change
    timestamp: Optional[datetime] = None

    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = datetime.utcnow()
