"""BLCE Pydantic contracts — data models for the comprehension engine."""
from __future__ import annotations

import uuid
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field

from .constants import (
    FilterOperator,
    IntakeSection,
    JoinType,
    MappingType,
    MeasureAggregation,
    ReportPatternType,
    SourceType,
    ToolClassification,
)


# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------

def _new_id(prefix: str = "blce") -> str:
    return f"{prefix}_{uuid.uuid4().hex[:12]}"


# ---------------------------------------------------------------------------
# Logic extraction primitives
# ---------------------------------------------------------------------------

class Measure(BaseModel):
    """A business measure extracted from source logic."""
    name: str = ""
    expression: str = ""                     # e.g. "SUM(revenue_amount)"
    aggregation: MeasureAggregation = MeasureAggregation.CUSTOM
    source_columns: List[str] = Field(default_factory=list)
    source_table: str = ""
    alias: str = ""                          # AS clause alias
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class FilterPredicate(BaseModel):
    """A single filter condition extracted from WHERE/HAVING."""
    column: str = ""
    operator: FilterOperator = FilterOperator.EQ
    value: str = ""                          # Stringified value
    source_clause: str = ""                  # Original SQL fragment
    is_having: bool = False


class JoinClause(BaseModel):
    """A join relationship extracted from SQL."""
    left_table: str = ""
    right_table: str = ""
    join_type: JoinType = JoinType.INNER
    left_key: str = ""
    right_key: str = ""
    condition: str = ""                      # Full ON clause


class GrainColumn(BaseModel):
    """A column that defines the reporting grain (GROUP BY)."""
    column: str = ""
    table: str = ""
    alias: str = ""


class Dependency(BaseModel):
    """A table/view/CTE referenced by the source logic."""
    name: str = ""
    schema_name: str = ""
    database: str = ""
    dep_type: str = "table"                  # table | view | cte | subquery


# ---------------------------------------------------------------------------
# Composite logic objects
# ---------------------------------------------------------------------------

class LogicObjects(BaseModel):
    """All logic elements extracted from a single source."""
    measures: List[Measure] = Field(default_factory=list)
    filters: List[FilterPredicate] = Field(default_factory=list)
    joins: List[JoinClause] = Field(default_factory=list)
    grain_columns: List[GrainColumn] = Field(default_factory=list)
    dependencies: List[Dependency] = Field(default_factory=list)


class LogicArtifact(BaseModel):
    """
    A single unit of business logic extracted from one source file/query.

    This is the core data structure that flows through the entire BLCE pipeline.
    """
    artifact_id: str = Field(default_factory=lambda: _new_id("art"))
    client_id: str = ""
    source_type: SourceType = SourceType.SQL
    source_path: str = ""                    # File path or query identifier
    source_name: str = ""                    # Human-readable name
    raw_source: str = ""                     # Original source text (truncated for large files)
    grain: str = ""                          # Summarised grain description
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    objects: LogicObjects = Field(default_factory=LogicObjects)
    explanation: str = ""                    # AI-generated or parser-generated explanation
    agent_trace_id: str = ""                 # Links to agent conversation
    extracted_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    metadata: Dict[str, Any] = Field(default_factory=dict)


# ---------------------------------------------------------------------------
# Cross-reference contracts
# ---------------------------------------------------------------------------

class CrossReferenceMapping(BaseModel):
    """Maps an entity from one system to another (or to a conformed dimension)."""
    xref_id: str = Field(default_factory=lambda: _new_id("xref"))
    domain: str = ""                         # Business domain (e.g. "well", "account")
    source_system: str = ""
    source_key: str = ""                     # Column or entity name in source
    target_system: str = ""                  # Could be "conformed" for unified model
    target_key: str = ""
    source_grain: str = ""
    target_grain: str = ""
    mapping_type: MappingType = MappingType.DIRECT
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    effective_from: str = ""
    effective_to: Optional[str] = None
    rationale: str = ""


class CrossReferenceModel(BaseModel):
    """Complete cross-reference model for a BLCE run."""
    model_id: str = Field(default_factory=lambda: _new_id("xmodel"))
    run_id: str = ""
    mappings: List[CrossReferenceMapping] = Field(default_factory=list)
    domains: List[str] = Field(default_factory=list)
    source_systems: List[str] = Field(default_factory=list)
    created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ---------------------------------------------------------------------------
# Grain contract
# ---------------------------------------------------------------------------

class GrainContract(BaseModel):
    """Defines the agreed grain for a business entity across systems."""
    contract_id: str = Field(default_factory=lambda: _new_id("grain"))
    entity: str = ""                         # e.g. "revenue", "production"
    conformed_grain: List[str] = Field(default_factory=list)  # e.g. ["well", "month"]
    source_grains: Dict[str, List[str]] = Field(default_factory=dict)
    # e.g. {"enertia": ["well_id", "month"], "wolfepak": ["well_no", "period"]}
    alignment_notes: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


# ---------------------------------------------------------------------------
# Normalized measure (post-normalization, Phase 2)
# ---------------------------------------------------------------------------

class NormalizedMeasure(BaseModel):
    """A measure after deduplication and normalization across artifacts."""
    measure_id: str = Field(default_factory=lambda: _new_id("meas"))
    canonical_name: str = ""                 # e.g. "net_revenue"
    business_name: str = ""                  # e.g. "Net Revenue"
    definition: str = ""                     # e.g. "gross_revenue - taxes - royalties"
    source_artifacts: List[str] = Field(default_factory=list)  # artifact_ids
    source_expressions: List[str] = Field(default_factory=list)
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    conflicts: List[str] = Field(default_factory=list)  # Conflicting definitions


# ---------------------------------------------------------------------------
# Evidence sample (for audit/masking)
# ---------------------------------------------------------------------------

class EvidenceSample(BaseModel):
    """A small data sample used to validate extracted logic."""
    sample_id: str = Field(default_factory=lambda: _new_id("samp"))
    artifact_id: str = ""
    table_name: str = ""
    filter_desc: str = ""                    # e.g. "status='Active', month IN (2025-01, 2025-02)"
    row_count: int = 0
    columns: List[str] = Field(default_factory=list)
    sample_hash: str = ""                    # Hash for integrity verification
    masked: bool = False                     # Whether DataShield masking was applied


# ---------------------------------------------------------------------------
# Governance
# ---------------------------------------------------------------------------

class GovernanceDecision(BaseModel):
    """Classification decision for a logic artifact or tool."""
    decision_id: str = Field(default_factory=lambda: _new_id("gov"))
    artifact_id: str = ""
    classification: ToolClassification = ToolClassification.CANDIDATE
    rationale: str = ""
    client_count: int = 0                    # How many clients use this pattern
    decided_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ---------------------------------------------------------------------------
# Skill manifest (generated skill prompts)
# ---------------------------------------------------------------------------

class SkillManifest(BaseModel):
    """Metadata for an auto-generated skill prompt from CORE artifacts."""
    skill_id: str = Field(default_factory=lambda: _new_id("skill"))
    artifact_id: str = ""
    client_id: str = ""
    skill_name: str = ""                     # e.g. "revenue_analysis"
    skill_path: str = ""                     # e.g. "skills/blce_revenue_analysis.txt"
    content: str = ""                        # Full skill prompt text
    classification: ToolClassification = ToolClassification.CORE
    measures_covered: List[str] = Field(default_factory=list)
    filters_covered: List[str] = Field(default_factory=list)
    grain_columns: List[str] = Field(default_factory=list)
    dependencies: List[str] = Field(default_factory=list)
    generated_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ---------------------------------------------------------------------------
# AI Agent contracts (Phase 9)
# ---------------------------------------------------------------------------

class SemanticAnalysis(BaseModel):
    """Result of AI semantic analysis on a logic artifact."""
    analysis_id: str = Field(default_factory=lambda: _new_id("sem"))
    artifact_id: str = ""
    original_confidence: float = 0.0
    boosted_confidence: float = 0.0
    business_intent: str = ""           # Plain-English description of what the logic does
    domain_tags: List[str] = Field(default_factory=list)  # e.g. ["revenue", "well"]
    measure_explanations: Dict[str, str] = Field(default_factory=dict)
    warnings: List[str] = Field(default_factory=list)
    agent_name: str = "ReportSemanticAgent"
    analyzed_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class QualityPolicy(BaseModel):
    """Quality expectations derived from a logic artifact."""
    policy_id: str = Field(default_factory=lambda: _new_id("qpol"))
    artifact_id: str = ""
    expectations: List[Dict[str, Any]] = Field(default_factory=list)
    # Each expectation: {"column": ..., "rule": ..., "severity": ..., "description": ...}
    coverage_score: float = 0.0         # 0-1, how much of the artifact is covered
    agent_name: str = "QualityPolicyAgent"
    generated_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class AgentTrace(BaseModel):
    """Trace of an agent invocation for audit/debugging."""
    trace_id: str = Field(default_factory=lambda: _new_id("trace"))
    agent_name: str = ""
    artifact_ids: List[str] = Field(default_factory=list)
    action: str = ""                    # e.g. "semantic_analysis", "cross_reference", "quality_policy"
    input_summary: str = ""
    output_summary: str = ""
    duration_seconds: float = 0.0
    success: bool = True
    error: Optional[str] = None
    created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ---------------------------------------------------------------------------
# Runtime result (Phase 11 — multi-agent runtime)
# ---------------------------------------------------------------------------

class RuntimeResult(BaseModel):
    """Summary of an AgentRuntime execution."""
    runtime_id: str = Field(default_factory=lambda: _new_id("rt"))
    tasks_submitted: int = 0
    tasks_completed: int = 0
    tasks_failed: int = 0
    tasks_skipped: int = 0
    total_duration: float = 0.0
    level_count: int = 0
    messages_published: int = 0
    task_details: List[Dict[str, Any]] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Multi-Provider Agent Swarm contracts (Phase 13)
# ---------------------------------------------------------------------------

class ProviderScore(BaseModel):
    """Routing score for a provider on a given task type."""
    provider: str = ""
    score: float = 0.0
    reason: str = ""
    cost_weight: float = 1.0
    latency_weight: float = 1.0


class AgentConversation(BaseModel):
    """Trace of an agent conversation for audit/persistence."""
    conversation_id: str = Field(default_factory=lambda: _new_id("conv"))
    agent_name: str = ""
    provider: str = "deterministic"
    turns: List[Dict[str, Any]] = Field(default_factory=list)
    total_tokens: int = 0
    duration_seconds: float = 0.0
    success: bool = True
    error: Optional[str] = None
    created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ---------------------------------------------------------------------------
# E2E→BLCE Chaining + Model Alteration contracts (Phase 14)
# ---------------------------------------------------------------------------

class E2EHandoff(BaseModel):
    """Handoff payload from an E2E pipeline run into BLCE."""
    handoff_id: str = Field(default_factory=lambda: _new_id("e2e"))
    e2e_run_id: str = ""
    source_dir: str = ""
    table_profiles: List[Dict[str, Any]] = Field(default_factory=list)
    classifications: List[Dict[str, Any]] = Field(default_factory=list)
    relationships: List[Dict[str, Any]] = Field(default_factory=list)
    table_summaries: List[Dict[str, Any]] = Field(default_factory=list)
    run_summary: Dict[str, Any] = Field(default_factory=dict)
    loaded_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class Alteration(BaseModel):
    """A single tracked model alteration (rename, reclassify, add rel, etc.)."""
    alteration_id: str = Field(default_factory=lambda: _new_id("alt"))
    action: str = ""
    target: str = ""
    old_value: str = ""
    new_value: str = ""
    rationale: str = ""
    applied_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    reversible: bool = True


class AlteredModel(BaseModel):
    """Snapshot of a source-system model after alterations are applied."""
    model_id: str = Field(default_factory=lambda: _new_id("amod"))
    source_system: str = ""
    tables: List[Dict[str, Any]] = Field(default_factory=list)
    alterations: List[Alteration] = Field(default_factory=list)
    created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class UnifiedModel(BaseModel):
    """Merged model across multiple source systems with conformed dimensions."""
    unified_id: str = Field(default_factory=lambda: _new_id("uni"))
    source_systems: List[str] = Field(default_factory=list)
    conformed_dimensions: List[Dict[str, Any]] = Field(default_factory=list)
    fact_tables: List[Dict[str, Any]] = Field(default_factory=list)
    merge_conflicts: List[str] = Field(default_factory=list)
    created_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ---------------------------------------------------------------------------
# Client Interaction contracts (Phase 15)
# ---------------------------------------------------------------------------

class ModelContext(BaseModel):
    """Assembled context for answering a question about the data model."""
    context_id: str = Field(default_factory=lambda: _new_id("ctx"))
    client_id: str = ""
    question: str = ""
    relevant_skills: List[Dict[str, Any]] = Field(default_factory=list)
    relevant_measures: List[Dict[str, Any]] = Field(default_factory=list)
    relevant_dimensions: List[str] = Field(default_factory=list)
    relevant_xrefs: List[Dict[str, Any]] = Field(default_factory=list)
    bus_matrix_excerpt: Dict[str, Any] = Field(default_factory=dict)
    alteration_history: List[Dict[str, Any]] = Field(default_factory=list)
    governance_summary: Dict[str, Any] = Field(default_factory=dict)
    assembled_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class GeneratedQuery(BaseModel):
    """A SQL query generated from NL intent + model metadata."""
    query_id: str = Field(default_factory=lambda: _new_id("qry"))
    intent: str = ""
    sql: str = ""
    tables_used: List[str] = Field(default_factory=list)
    measures_used: List[str] = Field(default_factory=list)
    grain_columns: List[str] = Field(default_factory=list)
    filters_applied: List[str] = Field(default_factory=list)
    confidence: float = 0.0
    warnings: List[str] = Field(default_factory=list)
    generated_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class ReportSuggestion(BaseModel):
    """A suggested report structure."""
    suggestion_id: str = Field(default_factory=lambda: _new_id("rpt"))
    report_type: str = ""
    title: str = ""
    description: str = ""
    measures: List[str] = Field(default_factory=list)
    dimensions: List[str] = Field(default_factory=list)
    filters: List[str] = Field(default_factory=list)
    sql_template: str = ""
    confidence: float = 0.0
    suggested_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class ModelChangeProposal(BaseModel):
    """A parsed change proposal from NL description."""
    proposal_id: str = Field(default_factory=lambda: _new_id("prop"))
    description: str = ""
    parsed_actions: List[Dict[str, Any]] = Field(default_factory=list)
    impact_summary: str = ""
    risk_level: str = "low"  # low | medium | high
    requires_review: bool = False
    proposed_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class ModelEvolutionEntry(BaseModel):
    """A single entry in the model evolution timeline."""
    entry_id: str = Field(default_factory=lambda: _new_id("evo"))
    timestamp: str = ""
    action: str = ""
    target: str = ""
    old_value: str = ""
    new_value: str = ""
    rationale: str = ""
    actor: str = ""  # "user" | "system" | "agent"


# ---------------------------------------------------------------------------
# Run summary
# ---------------------------------------------------------------------------

class BLCERunSummary(BaseModel):
    """Summary of a complete BLCE pipeline run."""
    run_id: str = Field(default_factory=lambda: _new_id("run"))
    client_id: str = ""
    started_at: str = ""
    completed_at: str = ""
    duration_seconds: float = 0.0
    source_files: int = 0
    artifacts_extracted: int = 0
    measures_found: int = 0
    filters_found: int = 0
    joins_found: int = 0
    cross_references: int = 0
    quality_suggestions: int = 0
    phases_completed: List[str] = Field(default_factory=list)
    phases_skipped: List[str] = Field(default_factory=list)
    errors: List[str] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Consultant Intake contracts
# ---------------------------------------------------------------------------

class IntakeQuestion(BaseModel):
    """A single question in the consultant intake questionnaire."""
    section: IntakeSection = IntakeSection.ORGANIZATION
    question_id: str = ""
    question_text: str = ""
    question_type: str = "text"  # text | select | multiselect | number | boolean
    options: List[str] = Field(default_factory=list)
    auto_populated: bool = False
    auto_value: str = ""
    response: str = ""


class IntakeQuestionnaire(BaseModel):
    """Complete consultant intake questionnaire for a client engagement."""
    questionnaire_id: str = Field(default_factory=lambda: _new_id("intake"))
    client_id: str = ""
    e2e_handoff_id: str = ""
    erp_type: str = ""
    sections: Dict[str, List[IntakeQuestion]] = Field(default_factory=dict)
    departments: List[str] = Field(default_factory=list)
    business_questions: List[str] = Field(default_factory=list)
    kpis: List[str] = Field(default_factory=list)
    pain_points: List[str] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Report & Notes Processing contracts
# ---------------------------------------------------------------------------

class ExtractedKPI(BaseModel):
    """A KPI extracted from a report, meeting notes, or questionnaire."""
    kpi_id: str = Field(default_factory=lambda: _new_id("kpi"))
    name: str = ""
    business_name: str = ""
    formula: str = ""
    unit: str = ""
    source: str = ""
    source_type: str = ""  # report | notes | questionnaire
    granularity: str = ""
    departments: List[str] = Field(default_factory=list)
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class ExtractedDimension(BaseModel):
    """A dimension extracted from a report, meeting notes, or questionnaire."""
    dimension_id: str = Field(default_factory=lambda: _new_id("edim"))
    name: str = ""
    business_name: str = ""
    hierarchy_levels: List[str] = Field(default_factory=list)
    source_tables: List[str] = Field(default_factory=list)
    key_columns: List[str] = Field(default_factory=list)
    attributes: List[str] = Field(default_factory=list)
    scd_type: int = 1  # 0, 1, or 2
    extracted_from: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class ExtractedFilter(BaseModel):
    """A business filter extracted from reports or meeting notes."""
    filter_id: str = Field(default_factory=lambda: _new_id("eflt"))
    description: str = ""
    sql_expression: str = ""
    applies_to: List[str] = Field(default_factory=list)
    source: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class ExcelWizardEvidence(BaseModel):
    """Evidence pointer to workbook structures used for a suggestion."""
    evidence_id: str = Field(default_factory=lambda: _new_id("xwev"))
    sheet_name: str = ""
    cell_ref: str = ""
    range_ref: str = ""
    formula: str = ""
    named_range: str = ""
    pivot_name: str = ""
    explanation: str = ""


class ExcelFormulaNode(BaseModel):
    """A parsed formula node in the workbook graph."""
    node_id: str = Field(default_factory=lambda: _new_id("xfn"))
    sheet_name: str = ""
    cell_ref: str = ""
    formula: str = ""
    dependencies: List[str] = Field(default_factory=list)


class ExcelPivotIntent(BaseModel):
    """Pivot layout interpreted as business intent."""
    pivot_id: str = Field(default_factory=lambda: _new_id("xpv"))
    sheet_name: str = ""
    pivot_name: str = ""
    rows: List[str] = Field(default_factory=list)
    columns: List[str] = Field(default_factory=list)
    values: List[str] = Field(default_factory=list)
    filters: List[str] = Field(default_factory=list)
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class ExcelNamedRangeIntent(BaseModel):
    """Named range interpreted as a business concept."""
    intent_id: str = Field(default_factory=lambda: _new_id("xnr"))
    name: str = ""
    destinations: List[str] = Field(default_factory=list)
    inferred_role: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class ExcelHierarchyCandidate(BaseModel):
    """Potential hierarchy extracted from report structure."""
    hierarchy_id: str = Field(default_factory=lambda: _new_id("xhc"))
    name: str = ""
    levels: List[str] = Field(default_factory=list)
    rationale: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class ExcelConfigCandidate(BaseModel):
    """Potential reusable configuration inferred from workbook logic."""
    config_id: str = Field(default_factory=lambda: _new_id("xcc"))
    config_type: str = ""  # rollup | mapping | semantic_rule | filter_set
    name: str = ""
    payload: Dict[str, Any] = Field(default_factory=dict)
    rationale: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)


class ExcelWizardSuggestion(BaseModel):
    """Typed wizard suggestion extracted from an Excel workbook."""
    suggestion_id: str = Field(default_factory=lambda: _new_id("xws"))
    suggestion_type: str = ""  # kpi_definition | grain_candidate | dimension_candidate | filter_rule | hierarchy_candidate | configuration_candidate
    title: str = ""
    description: str = ""
    proposed_payload: Dict[str, Any] = Field(default_factory=dict)
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    source: str = "deterministic"  # deterministic | copilot | hybrid
    evidence_refs: List[ExcelWizardEvidence] = Field(default_factory=list)
    status: str = "proposed"  # proposed | approved | rejected | deferred
    reviewer: str = ""
    review_comment: str = ""
    reviewed_at: str = ""


class ExcelWizardAnalysis(BaseModel):
    """Full Excel wizard output for one workbook."""
    analysis_id: str = Field(default_factory=lambda: _new_id("xwa"))
    file_path: str = ""
    workbook_name: str = ""
    formula_nodes: List[ExcelFormulaNode] = Field(default_factory=list)
    named_ranges: List[ExcelNamedRangeIntent] = Field(default_factory=list)
    pivots: List[ExcelPivotIntent] = Field(default_factory=list)
    hierarchy_candidates: List[ExcelHierarchyCandidate] = Field(default_factory=list)
    config_candidates: List[ExcelConfigCandidate] = Field(default_factory=list)
    suggestions: List[ExcelWizardSuggestion] = Field(default_factory=list)
    complexity_score: float = 0.0
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    warnings: List[str] = Field(default_factory=list)
    metadata: Dict[str, Any] = Field(default_factory=dict)


class ReportAnalysis(BaseModel):
    """Analysis of a single report (Excel or PDF)."""
    analysis_id: str = Field(default_factory=lambda: _new_id("rpta"))
    file_path: str = ""
    report_type: ReportPatternType = ReportPatternType.CUSTOM
    kpis: List[ExtractedKPI] = Field(default_factory=list)
    dimensions: List[ExtractedDimension] = Field(default_factory=list)
    filters: List[ExtractedFilter] = Field(default_factory=list)
    comparisons: List[str] = Field(default_factory=list)
    aggregation_levels: List[str] = Field(default_factory=list)
    excel_wizard_id: str = ""
    wizard_suggestions: List[ExcelWizardSuggestion] = Field(default_factory=list)
    wizard_confidence: float = 0.0


class ReconciliationStatus(str, Enum):
    """Status of one Excel-vs-warehouse comparison row."""
    MATCH = "match"
    DISCREPANCY = "discrepancy"
    MISSING_IN_WAREHOUSE = "missing_in_warehouse"
    EXTRA_IN_WAREHOUSE = "extra_in_warehouse"


class ExcelWarehouseCellResult(BaseModel):
    """Comparison result for one metric/slice key."""
    row_id: str = Field(default_factory=lambda: _new_id("xwr"))
    metric_key: str = ""
    excel_value: Optional[float] = None
    warehouse_value: Optional[float] = None
    difference: float = 0.0
    difference_pct: float = 0.0
    status: ReconciliationStatus = ReconciliationStatus.DISCREPANCY
    classification_type: str = ""  # grain | filter | hierarchy | join | signage | rounding | missing_data | extra_data | formula
    classification_confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    root_cause: str = ""
    suggested_fix: str = ""
    dw_query: str = ""
    metadata: Dict[str, Any] = Field(default_factory=dict)


class ExcelWarehouseReconciliation(BaseModel):
    """Full reconciliation output for an Excel report against warehouse values."""
    reconciliation_id: str = Field(default_factory=lambda: _new_id("xrec"))
    analysis_id: str = ""
    report_name: str = ""
    tolerance_pct: float = 0.01
    compared_count: int = 0
    matched_count: int = 0
    discrepancy_count: int = 0
    missing_in_warehouse_count: int = 0
    extra_in_warehouse_count: int = 0
    classifications: Dict[str, int] = Field(default_factory=dict)
    results: List[ExcelWarehouseCellResult] = Field(default_factory=list)
    generated_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class MeetingNotesAnalysis(BaseModel):
    """Analysis of meeting notes text."""
    analysis_id: str = Field(default_factory=lambda: _new_id("mna"))
    file_path: str = ""
    kpis: List[ExtractedKPI] = Field(default_factory=list)
    dimensions: List[ExtractedDimension] = Field(default_factory=list)
    filters: List[ExtractedFilter] = Field(default_factory=list)
    business_rules: List[str] = Field(default_factory=list)
    open_questions: List[str] = Field(default_factory=list)


class DepartmentMapping(BaseModel):
    """Maps a department to data domains and access requirements."""
    department: str = ""
    data_domains: List[str] = Field(default_factory=list)
    primary_contact: str = ""
    reporting_tool: str = ""
    access_level: str = "read"
    security_requirements: List[str] = Field(default_factory=list)


# ---------------------------------------------------------------------------
# Consultant Review contracts (P3.3)
# ---------------------------------------------------------------------------

class ReviewStatus(str, Enum):
    PROPOSED = "proposed"
    REVIEWED = "reviewed"
    APPROVED = "approved"
    REJECTED = "rejected"
    BUILT = "built"


class ReviewEntry(BaseModel):
    """Audit trail entry for a consultant review action."""
    review_id: str = Field(default_factory=lambda: _new_id("rev"))
    object_type: str = ""       # "dimension" | "fact"
    object_id: str = ""         # dimension_id or fact_id
    object_name: str = ""
    status: ReviewStatus = ReviewStatus.PROPOSED
    reviewer: str = ""
    comments: str = ""
    changes_made: Dict[str, Any] = Field(default_factory=dict)
    reviewed_at: str = ""
    approved_at: str = ""


# ---------------------------------------------------------------------------
# AI Business Model Generation contracts
# ---------------------------------------------------------------------------

class ProposedDimension(BaseModel):
    """A proposed Kimball dimension table."""
    dimension_id: str = Field(default_factory=lambda: _new_id("pdim"))
    name: str = ""                           # e.g. "DIM_WELL"
    business_name: str = ""                  # e.g. "Well Dimension"
    source_tables: List[str] = Field(default_factory=list)
    key_column: str = ""                     # e.g. "WELL_SK"
    natural_key: str = ""                    # e.g. "WELL_ID"
    attributes: List[str] = Field(default_factory=list)
    hierarchy_levels: List[str] = Field(default_factory=list)
    scd_type: int = 1
    rationale: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    review_status: str = "proposed"          # P3.3 review workflow


class ProposedFact(BaseModel):
    """A proposed Kimball fact table."""
    fact_id: str = Field(default_factory=lambda: _new_id("pfct"))
    name: str = ""                           # e.g. "FACT_FINANCIAL_ACTUALS"
    business_name: str = ""                  # e.g. "Financial Actuals"
    grain: str = ""                          # e.g. "account x cost_center x month"
    grain_columns: List[str] = Field(default_factory=list)
    measures: List[str] = Field(default_factory=list)
    dimension_fks: List[str] = Field(default_factory=list)
    source_tables: List[str] = Field(default_factory=list)
    needs_union: bool = False
    union_sources: List[str] = Field(default_factory=list)
    rationale: str = ""
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    review_status: str = "proposed"          # P3.3 review workflow


class MeasureValidation(BaseModel):
    """Validation result for a single proposed measure against source tables (P2.2)."""
    fact_name: str = ""
    measure_name: str = ""
    exists_in_source: bool = True
    is_numeric: bool = True
    actual_data_type: str = ""
    warning: str = ""
    severity: str = "info"  # info | warning | error


class MeasureConflict(BaseModel):
    """A conflict between measure definitions from different sources."""
    conflict_id: str = Field(default_factory=lambda: _new_id("mcon"))
    measure_name: str = ""
    definitions: List[Dict[str, Any]] = Field(default_factory=list)
    resolution: str = ""
    resolution_type: str = ""               # unified | separate | escalated
    status: str = "open"                    # open | resolved | escalated


class ProposedModel(BaseModel):
    """Complete proposed Kimball data warehouse model."""
    model_id: str = Field(default_factory=lambda: _new_id("pmod"))
    client_id: str = ""
    dimensions: List[ProposedDimension] = Field(default_factory=list)
    facts: List[ProposedFact] = Field(default_factory=list)
    bus_matrix: Dict[str, Any] = Field(default_factory=dict)
    measure_dictionary: Dict[str, str] = Field(default_factory=dict)
    conflicts: List[MeasureConflict] = Field(default_factory=list)
    business_rules: List[str] = Field(default_factory=list)
    mapping_report: Optional[Dict[str, Any]] = None  # P1.1 report-to-model mapping
    measure_validations: List[MeasureValidation] = Field(default_factory=list)  # P2.2
    review_log: List[ReviewEntry] = Field(default_factory=list)  # P3.3 review audit trail


# ---------------------------------------------------------------------------
# Auto-Build contracts
# ---------------------------------------------------------------------------

class GeneratedDDL(BaseModel):
    """Generated Snowflake DDL for a dimension or fact table."""
    ddl_id: str = Field(default_factory=lambda: _new_id("ddl"))
    object_name: str = ""
    object_type: str = ""                   # dimension | fact
    ddl_sql: str = ""
    column_comments: Dict[str, str] = Field(default_factory=dict)
    source_object: str = ""


class GeneratedWrightPipeline(BaseModel):
    """Generated Wright 4-object pipeline SQL for a fact table."""
    pipeline_id: str = Field(default_factory=lambda: _new_id("wrt"))
    fact_name: str = ""
    vw1_sql: str = ""
    dt2_sql: str = ""
    dt3a_sql: str = ""
    dt3_sql: str = ""
    union_template: str = ""


class GeneratedExpectation(BaseModel):
    """A generated quality expectation for a proposed model object."""
    expectation_id: str = Field(default_factory=lambda: _new_id("gexp"))
    table_name: str = ""
    column_name: str = ""
    expectation_type: str = ""
    kwargs: Dict[str, Any] = Field(default_factory=dict)
    severity: str = "medium"
    rationale: str = ""


class SemanticLayerDefinition(BaseModel):
    """A semantic layer entry for BI tools (Cortex Analyst compatible)."""
    entry_id: str = Field(default_factory=lambda: _new_id("sem"))
    entry_type: str = ""                    # dimension | measure | time_dimension
    name: str = ""
    business_name: str = ""
    definition: str = ""
    table: str = ""
    column: str = ""
    data_type: str = ""
    aggregation: str = ""


# ---------------------------------------------------------------------------
# Deployment contracts (P5.2)
# ---------------------------------------------------------------------------

class DeploymentStatus(str, Enum):
    PENDING = "pending"
    RUNNING = "running"
    SUCCESS = "success"
    FAILED = "failed"
    SKIPPED = "skipped"


class DDLExecutionResult(BaseModel):
    """Result of executing a single DDL statement on Snowflake."""
    result_id: str = Field(default_factory=lambda: _new_id("ddlres"))
    ddl_id: str = ""
    object_name: str = ""
    object_type: str = ""                   # dimension | fact
    status: DeploymentStatus = DeploymentStatus.PENDING
    executed_sql: str = ""
    rows_affected: int = 0
    execution_time_ms: float = 0.0
    error_message: str = ""
    executed_at: str = ""


class DeploymentSummary(BaseModel):
    """Summary of a DDL deployment run."""
    deployment_id: str = Field(default_factory=lambda: _new_id("deploy"))
    run_id: str = ""
    ddl_results: List[DDLExecutionResult] = Field(default_factory=list)
    total_objects_deployed: int = 0
    total_failures: int = 0
    total_execution_time_ms: float = 0.0
    deployed_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


# ---------------------------------------------------------------------------
# Report-to-Model Mapping contracts (P1.1)
# ---------------------------------------------------------------------------

class KPIFactMapping(BaseModel):
    """Maps an extracted KPI to a proposed fact table."""
    kpi_name: str = ""
    kpi_business_name: str = ""
    proposed_fact: Optional[str] = None     # fact name or None if unmapped
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    rationale: str = ""


class DimDimMapping(BaseModel):
    """Maps an extracted dimension to a proposed dimension."""
    extracted_dim: str = ""
    extracted_business_name: str = ""
    proposed_dim: Optional[str] = None      # proposed dim name or None
    confidence: float = Field(ge=0.0, le=1.0, default=0.0)
    rationale: str = ""


class MappingReport(BaseModel):
    """Report mapping extracted report items to proposed model objects."""
    report_id: str = Field(default_factory=lambda: _new_id("mmap"))
    kpi_mappings: List[KPIFactMapping] = Field(default_factory=list)
    dimension_mappings: List[DimDimMapping] = Field(default_factory=list)
    unmapped_kpis: List[str] = Field(default_factory=list)
    unmapped_dims: List[str] = Field(default_factory=list)
    coverage_score: float = 0.0             # fraction of items mapped


# ---------------------------------------------------------------------------
# Graph Copilot & Reconciliation contracts (Section 7f)
# ---------------------------------------------------------------------------

class DiscrepancyType(str, Enum):
    MATCH = "match"
    ROUNDING = "rounding"
    GENUINE_DIFF = "genuine_diff"
    MISSING_EXCEL = "missing_excel"
    MISSING_WAREHOUSE = "missing_warehouse"


class RootCause(str, Enum):
    ROUNDING = "rounding"
    FILTER_MISMATCH = "filter_mismatch"
    FORMULA_DIFF = "formula_diff"
    MISSING_DATA = "missing_data"
    TIMING = "timing"
    UNKNOWN = "unknown"


class ExcelFormula(BaseModel):
    """A formula extracted from an Excel cell."""
    cell_address: str = ""
    formula: str = ""
    computed_value: Any = None
    sheet_name: str = ""


class NamedRange(BaseModel):
    """A named range definition from an Excel workbook."""
    name: str = ""
    refers_to: str = ""
    scope: str = "workbook"
    value: Any = None


class PivotTableConfig(BaseModel):
    """Configuration of a pivot table found in an Excel workbook."""
    name: str = ""
    source_range: str = ""
    row_fields: List[str] = Field(default_factory=list)
    column_fields: List[str] = Field(default_factory=list)
    value_fields: List[str] = Field(default_factory=list)
    filter_fields: List[str] = Field(default_factory=list)


class WorkbookExtraction(BaseModel):
    """Complete extraction result from an Excel workbook."""
    extraction_id: str = Field(default_factory=lambda: _new_id("wbex"))
    file_path: str = ""
    source: str = "local"
    sheets: List[str] = Field(default_factory=list)
    formulas: List[ExcelFormula] = Field(default_factory=list)
    named_ranges: List[NamedRange] = Field(default_factory=list)
    pivot_tables: List[PivotTableConfig] = Field(default_factory=list)
    measures: List[Measure] = Field(default_factory=list)
    artifact: Optional[LogicArtifact] = None
    extracted_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class CellReconciliation(BaseModel):
    """Reconciliation result for a single cell comparison."""
    cell_address: str = ""
    excel_value: Any = None
    warehouse_value: Any = None
    discrepancy_type: DiscrepancyType = DiscrepancyType.MATCH
    difference: float = 0.0
    mapping_label: str = ""


class ReconciliationReport(BaseModel):
    """Full report from an Excel-vs-warehouse reconciliation."""
    report_id: str = Field(default_factory=lambda: _new_id("recon"))
    total_cells: int = 0
    matched: int = 0
    rounding: int = 0
    genuine_diff: int = 0
    missing_excel: int = 0
    missing_warehouse: int = 0
    cells: List[CellReconciliation] = Field(default_factory=list)
    rounding_threshold: float = 0.01
    reconciled_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())


class Fix(BaseModel):
    """A generated fix for a reconciliation discrepancy."""
    fix_id: str = Field(default_factory=lambda: _new_id("fix"))
    root_cause: RootCause = RootCause.UNKNOWN
    description: str = ""
    fix_type: str = ""
    sql: str = ""
    wright_object: str = ""
    confidence: float = 0.0


class ResolutionReport(BaseModel):
    """Report of all fixes generated for reconciliation discrepancies."""
    resolution_id: str = Field(default_factory=lambda: _new_id("resol"))
    reconciliation_id: str = ""
    total_discrepancies: int = 0
    auto_fixable: int = 0
    manual_review: int = 0
    fixes: List[Fix] = Field(default_factory=list)
    summary: str = ""
    resolved_at: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())

