use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdentitySpec {
    pub api_version: String,
    pub identity_version: String,
    pub entity: EntitySpec,
    pub sources: Vec<SourceSpec>,
    #[serde(default)]
    pub reference_identifiers: Option<Vec<ReferenceIdentifierSpec>>,
    #[serde(default)]
    pub blocking: Option<BlockingSpec>,
    pub rules: Vec<RuleSpec>,
    #[serde(default)]
    pub survivorship: Option<SurvivorshipSpec>,
    #[serde(default)]
    pub relations: Option<Vec<RelationSpec>>,
    pub decision: DecisionSpec,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EntitySpec {
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceSpec {
    pub name: String,
    // Adapter-first fields
    #[serde(default)]
    pub adapter: Option<AdapterType>,
    #[serde(default)]
    pub location: Option<String>,
    #[serde(default)]
    pub primary_key: Option<String>,
    #[serde(default)]
    pub schema: Option<HashMap<String, FieldSchema>>,
    #[serde(default)]
    pub sampling: Option<SamplingSpec>,
    #[serde(default)]
    pub tags: Vec<String>,
    // Legacy fields
    #[serde(default)]
    pub system: Option<String>,
    #[serde(default)]
    pub table: Option<String>,
    #[serde(default)]
    pub id: Option<String>,
    #[serde(default)]
    pub attributes: HashMap<String, String>,
    pub temporal: Option<TemporalSpec>,
    #[serde(default)]
    pub freshness: Option<FreshnessSpec>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AdapterType {
    Snowflake, Bigquery, Redshift, Databricks, Postgres,
    Pandas, Csv, Dbt, File, Webhook, Api,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldSchema {
    #[serde(rename = "type")]
    pub field_type: String,
    #[serde(default)]
    pub pii: bool,
    #[serde(default)]
    pub nullable: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SamplingSpec {
    pub strategy: SamplingStrategy,
    #[serde(default = "default_sample_rate")]
    pub rate: f64,
}

fn default_sample_rate() -> f64 { 1.0 }

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SamplingStrategy { Random, Stratified, TimeBased }

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FreshnessSpec {
    pub max_age: String,
    pub warn_after: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalSpec {
    pub valid_from: String,
    pub valid_to: Option<String>,
    pub strategy: crate::ir::TemporalStrategy,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReferenceIdentifierSpec {
    pub name: String,
    #[serde(rename = "type")]
    pub id_type: crate::ir::ReferenceIdType,
    pub authority: Option<String>,
    pub field: String,
    pub match_weight: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockingSpec {
    pub strategy: crate::ir::BlockingStrategy,
    pub keys: Vec<Vec<String>>,
    pub fallback: Option<BlockingFallback>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockingFallback {
    pub enabled: bool,
    pub sample_rate: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum RuleSpec {
    Exact {
        name: String,
        field: String,
        weight: f64,
        options: Option<ExactMatchOptions>,
    },
    Similarity {
        name: String,
        field: String,
        algorithm: crate::ir::SimilarityAlgorithm,
        threshold: f64,
        weight: f64,
        #[serde(default)]
        normalizer: Option<String>,
    },
    Range {
        name: String,
        field: String,
        tolerance: f64,
        weight: f64,
        #[serde(default)]
        normalizer: Option<String>,
    },
    Composite {
        name: String,
        operator: crate::ir::CompositeOperator,
        children: Vec<RuleSpec>,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExactMatchOptions {
    pub case_insensitive: bool,
    pub normalize: bool,
    #[serde(default)]
    pub normalizer: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurvivorshipSpec {
    pub default: crate::ir::SurvivorshipStrategy,
    #[serde(default)]
    pub overrides: Vec<SurvivorshipOverride>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurvivorshipOverride {
    pub field: String,
    pub strategy: crate::ir::SurvivorshipStrategy,
    #[serde(default)]
    pub priority: Vec<String>,
    pub function: Option<crate::survivorship::AggregateFunction>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelationSpec {
    pub name: String,
    pub from_entity: String,
    pub to_entity: String,
    pub join_key: String,
    pub cardinality: crate::ir::Cardinality,
    pub propagate_match: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecisionSpec {
    pub thresholds: ThresholdsSpec,
    pub conflict_strategy: crate::ir::ConflictStrategy,
    pub review_queue: Option<ReviewQueueSpec>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThresholdsSpec {
    #[serde(rename = "match")]
    pub match_threshold: f64,
    pub review: Option<f64>,
    pub reject: Option<f64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReviewQueueSpec {
    pub webhook: Option<String>,
}

impl IdentitySpec {
    pub fn from_yaml(yaml: &str) -> Result<Self, serde_yaml::Error> {
        serde_yaml::from_str(yaml)
    }
}
