"""
Nyaya Gate — Epistemological Verification
Based on the Nyaya school of Hindu philosophy (Aksapada Gautama, ~2nd century BCE).

Four Pramanas (valid means of knowledge):
1. Pratyaksha (Perception)   — Direct empirical evidence
2. Anumana (Inference)       — Logical deduction from evidence
3. Upamana (Comparison)      — Knowledge by analogy/similarity
4. Shabda (Testimony)        — Reliable authoritative source

Sixteen Categories (Padarthas) for structured reasoning:
- Pramana, Prameya, Samshaya, Prayojana, Drishtanta,
- Siddhanta, Avayava, Tarka, Nirnaya, Vada,
- Jalpa, Vitanda, Hetvabhasa, Chala, Jati, Nigrahasthana

A claim is verified by classifying its evidence into pramana types,
checking for hetvabhasa (fallacies), and scoring reliability.
"""

from enum import Enum, auto
from dataclasses import dataclass, field
from typing import Any, Optional


class PramanaType(Enum):
    """The four valid means of knowledge."""
    PRATYAKSHA = auto()   # Direct perception / empirical data
    ANUMANA = auto()      # Inference / logical deduction
    UPAMANA = auto()      # Comparison / analogy
    SHABDA = auto()       # Testimony / authoritative source


class Hetvabhasa(Enum):
    """Five fallacies of inference (invalid reasoning patterns)."""
    SAVYABHICHARA = "inconsistent"       # Reason present but conclusion absent elsewhere
    VIRUDDHA = "contradictory"           # Reason proves the opposite
    PRAKARANASAMA = "unproven"           # Reason itself needs proof
    SADHYASAMA = "circular"             # Reason is same as conclusion
    KALATITA = "mistimed"               # Reason applied at wrong time/context


@dataclass
class Evidence:
    """A piece of evidence supporting or opposing a claim."""
    value: Any
    pramana_type: PramanaType
    source: str = ""
    reliability: float = 1.0     # 0.0-1.0
    timestamp: str = ""
    metadata: dict = field(default_factory=dict)


@dataclass
class NyayaVerdict:
    """Result of Nyaya epistemological analysis."""
    is_valid: bool
    pramana_classification: dict       # Which pramana types support the claim
    fallacies_detected: list           # Any hetvabhasa found
    reliability_score: float           # Weighted reliability across all evidence
    reasoning_chain: list              # Step-by-step Nyaya reasoning
    recommendation: str                # "PROCEED" | "CAUTION" | "HALT"


# Reliability weights by pramana type
# Pratyaksha (direct data) > Anumana (inference) > Shabda (authority) > Upamana (analogy)
PRAMANA_WEIGHTS = {
    PramanaType.PRATYAKSHA: 1.0,
    PramanaType.ANUMANA: 0.85,
    PramanaType.SHABDA: 0.7,
    PramanaType.UPAMANA: 0.5,
}


class NyayaGate:
    """
    Nyaya epistemological verification gate.

    Classifies evidence by pramana type, detects reasoning fallacies,
    and produces a reliability-weighted verdict.

    Usage:
        gate = NyayaGate()
        verdict = gate.evaluate(
            claim="Server load is within safe limits",
            evidence=[
                Evidence(value=0.65, pramana_type=PramanaType.PRATYAKSHA,
                         source="cpu_monitor", reliability=0.95),
                Evidence(value="should be fine", pramana_type=PramanaType.UPAMANA,
                         source="similar_server", reliability=0.4),
            ],
            constraints={"max_load": 0.8}
        )
    """

    def __init__(self, min_reliability=0.6, require_pratyaksha=False,
                 custom_weights=None):
        self.min_reliability = min_reliability
        self.require_pratyaksha = require_pratyaksha
        self.weights = custom_weights or PRAMANA_WEIGHTS.copy()

    def classify_evidence(self, evidence_list: list) -> dict:
        """Classify evidence into pramana categories with counts and scores."""
        classification = {pt: [] for pt in PramanaType}
        for ev in evidence_list:
            classification[ev.pramana_type].append(ev)
        return classification

    def detect_fallacies(self, claim: str, evidence_list: list,
                         constraints: dict) -> list:
        """Detect hetvabhasa (reasoning fallacies) in the evidence."""
        fallacies = []

        # SAVYABHICHARA — inconsistent: evidence contradicts itself
        values = [e.value for e in evidence_list if e.value is not None]
        if len(values) >= 2:
            bool_values = [v for v in values if isinstance(v, bool)]
            if bool_values and True in bool_values and False in bool_values:
                fallacies.append({
                    "type": Hetvabhasa.SAVYABHICHARA.value,
                    "detail": "Evidence contains contradictory boolean values",
                    "severity": "high"
                })

            numeric_values = [v for v in values if isinstance(v, (int, float))]
            if len(numeric_values) >= 2:
                spread = max(numeric_values) - min(numeric_values)
                mean = sum(numeric_values) / len(numeric_values)
                if mean != 0 and spread / abs(mean) > 1.0:
                    fallacies.append({
                        "type": Hetvabhasa.SAVYABHICHARA.value,
                        "detail": f"Numeric evidence spread ({spread:.2f}) "
                                  f"exceeds mean ({mean:.2f})",
                        "severity": "medium"
                    })

        # VIRUDDHA — contradictory: evidence opposes the claim direction
        for key, constraint_val in constraints.items():
            matching = [e for e in evidence_list
                        if key in str(e.source) or key in str(e.metadata)]
            for ev in matching:
                if isinstance(ev.value, (int, float)) and isinstance(constraint_val, (int, float)):
                    if isinstance(constraint_val, (int, float)):
                        # Check if evidence directly violates constraint
                        if ev.value > constraint_val * 1.5:
                            fallacies.append({
                                "type": Hetvabhasa.VIRUDDHA.value,
                                "detail": f"Evidence {ev.source}={ev.value} "
                                          f"exceeds constraint {key}={constraint_val} by >50%",
                                "severity": "high"
                            })

        # PRAKARANASAMA — unproven: only low-reliability or analogy-based evidence
        high_quality = [e for e in evidence_list
                        if e.reliability >= 0.7
                        and e.pramana_type in (PramanaType.PRATYAKSHA, PramanaType.ANUMANA)]
        if not high_quality and evidence_list:
            fallacies.append({
                "type": Hetvabhasa.PRAKARANASAMA.value,
                "detail": "No high-reliability direct or inferential evidence provided",
                "severity": "medium"
            })

        # SADHYASAMA — circular: evidence source is the claim itself
        for ev in evidence_list:
            if ev.source and claim.lower() in ev.source.lower():
                fallacies.append({
                    "type": Hetvabhasa.SADHYASAMA.value,
                    "detail": f"Evidence source '{ev.source}' appears to "
                              f"reference the claim itself",
                    "severity": "high"
                })

        return fallacies

    def check_constraints(self, evidence_list: list, constraints: dict) -> list:
        """Check evidence values against hard constraints. Returns violations."""
        violations = []
        for key, limit in constraints.items():
            for ev in evidence_list:
                ev_key = ev.metadata.get("constraint_key") or ev.source
                if ev_key != key:
                    continue
                if isinstance(ev.value, (int, float)) and isinstance(limit, (int, float)):
                    if ev.value > limit:
                        violations.append({
                            "constraint": key,
                            "limit": limit,
                            "actual": ev.value,
                            "source": ev.source,
                            "exceeded_by": round(ev.value - limit, 4)
                        })
                elif isinstance(ev.value, bool) and isinstance(limit, bool):
                    if ev.value != limit:
                        violations.append({
                            "constraint": key,
                            "required": limit,
                            "actual": ev.value,
                            "source": ev.source
                        })
        return violations

    def compute_reliability(self, evidence_list: list) -> float:
        """Compute weighted reliability score across all evidence."""
        if not evidence_list:
            return 0.0
        total_weight = 0.0
        weighted_score = 0.0
        for ev in evidence_list:
            w = self.weights.get(ev.pramana_type, 0.5)
            total_weight += w
            weighted_score += ev.reliability * w
        return round(weighted_score / total_weight, 4) if total_weight > 0 else 0.0

    def evaluate(self, claim: str, evidence: list,
                 constraints: dict = None) -> NyayaVerdict:
        """
        Full Nyaya evaluation of a claim against evidence and constraints.

        Returns NyayaVerdict with recommendation: PROCEED / CAUTION / HALT
        """
        constraints = constraints or {}
        reasoning = []

        # Step 1: Classify evidence by pramana type
        classification = self.classify_evidence(evidence)
        pramana_summary = {
            pt.name: len(evs) for pt, evs in classification.items() if evs
        }
        reasoning.append(f"Evidence classified: {pramana_summary}")

        # Step 2: Check pratyaksha requirement
        if self.require_pratyaksha and not classification[PramanaType.PRATYAKSHA]:
            reasoning.append("HALT: No direct empirical evidence (pratyaksha) provided")
            return NyayaVerdict(
                is_valid=False,
                pramana_classification=pramana_summary,
                fallacies_detected=[],
                reliability_score=0.0,
                reasoning_chain=reasoning,
                recommendation="HALT"
            )

        # Step 3: Detect fallacies
        fallacies = self.detect_fallacies(claim, evidence, constraints)
        if fallacies:
            reasoning.append(
                f"Fallacies detected: {[f['type'] for f in fallacies]}"
            )
        else:
            reasoning.append("No reasoning fallacies detected")

        # Step 4: Check hard constraints
        violations = self.check_constraints(evidence, constraints)
        if violations:
            reasoning.append(
                f"Constraint violations: {[v['constraint'] for v in violations]}"
            )

        # Step 5: Compute reliability
        reliability = self.compute_reliability(evidence)
        reasoning.append(f"Weighted reliability: {reliability}")

        # Step 6: Determine verdict
        high_severity = [f for f in fallacies if f.get("severity") == "high"]

        if violations or high_severity:
            recommendation = "HALT"
            is_valid = False
            reasoning.append(
                f"HALT: {len(violations)} constraint violations, "
                f"{len(high_severity)} high-severity fallacies"
            )
        elif fallacies or reliability < self.min_reliability:
            recommendation = "CAUTION"
            is_valid = False
            reasoning.append(
                f"CAUTION: reliability {reliability} "
                f"(threshold: {self.min_reliability}), "
                f"{len(fallacies)} fallacies"
            )
        else:
            recommendation = "PROCEED"
            is_valid = True
            reasoning.append(
                f"PROCEED: reliability {reliability}, "
                f"no fallacies, no violations"
            )

        return NyayaVerdict(
            is_valid=is_valid,
            pramana_classification=pramana_summary,
            fallacies_detected=fallacies,
            reliability_score=reliability,
            reasoning_chain=reasoning,
            recommendation=recommendation
        )
