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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IdentityPlan {
    pub entity: String,
    pub plan_hash: String,
    pub identity_version: String,
    pub sources: Vec<ResolvedSource>,
    pub blocking: CompiledBlocking,
    pub match_graph: MatchGraph,
    pub survivorship: SurvivorshipPolicy,
    pub decision: DecisionConfig,
    pub reference_identifiers: Vec<CompiledReferenceIdentifier>,
    pub relations: Vec<CompiledRelation>,
    #[serde(default)]
    pub governance: Option<CompiledGovernance>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedSource {
    pub name: String,
    #[serde(alias = "system")]
    pub adapter: String,
    #[serde(alias = "table")]
    pub location: String,
    pub id_field: String,
    pub field_mappings: HashMap<String, String>,
    pub temporal: Option<TemporalConfig>,
    #[serde(default)]
    pub freshness: Option<CompiledFreshness>,
    #[serde(default)]
    pub schema: Option<HashMap<String, CompiledFieldSchema>>,
    #[serde(default)]
    pub sampling: Option<CompiledSampling>,
    #[serde(default)]
    pub tags: Vec<String>,
    #[serde(default)]
    pub pii_fields: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompiledFreshness {
    pub max_age_seconds: u64,
    pub warn_after_seconds: Option<u64>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompiledFieldSchema {
    pub field_type: String,
    pub pii: bool,
    pub nullable: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompiledSampling {
    pub strategy: String,
    pub rate: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompiledGovernance {
    pub require_freshness: bool,
    pub require_schema: bool,
    pub required_tags: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalConfig {
    pub valid_from_field: String,
    pub valid_to_field: Option<String>,
    pub strategy: TemporalStrategy,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum TemporalStrategy {
    #[default]
    LatestOnly,
    PointInTime,
    BiTemporal,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompiledBlocking {
    pub strategy: BlockingStrategy,
    pub keys: Vec<BlockingKey>,
    pub fallback_sample_rate: Option<f64>,
    pub lsh_bands: Option<usize>,
    pub lsh_rows: Option<usize>,
    pub neighborhood_window: Option<usize>,
    pub sort_fields: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum BlockingStrategy {
    Single,
    #[default]
    Composite,
    Lsh,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockingKey {
    pub fields: Vec<String>,
    pub transformations: Vec<BlockingTransformation>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BlockingTransformation {
    Lowercase,
    FirstInitial,
    Soundex,
    AreaCode,
    Last4,
    Trigrams,
    DoubleMetaphone,
    NormalizePhone,
    NormalizeEmail,
    NormalizeName,
    NormalizeDomain,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MatchGraph {
    pub rules: Vec<CompiledRule>,
    pub leaf_count: usize,
}

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

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

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SimilarityAlgorithm {
    JaroWinkler,
    Levenshtein,
    Soundex,
    Metaphone,
    Cosine,
    Haversine,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum CompositeOperator {
    And,
    Or,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SurvivorshipPolicy {
    pub default_strategy: SurvivorshipStrategy,
    pub field_policies: HashMap<String, FieldSurvivorshipPolicy>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SurvivorshipStrategy {
    #[default]
    MostRecent,
    MostComplete,
    SourcePriority,
    Aggregate,
    Custom,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldSurvivorshipPolicy {
    pub strategy: SurvivorshipStrategy,
    pub source_priority: Option<Vec<String>>,
    pub aggregate_fn: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecisionConfig {
    pub match_threshold: f64,
    pub review_threshold: Option<f64>,
    pub reject_threshold: Option<f64>,
    pub conflict_strategy: ConflictStrategy,
    pub review_webhook: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ConflictStrategy {
    #[default]
    PreferHighConfidence,
    PreferRecent,
    ManualReview,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompiledReferenceIdentifier {
    pub name: String,
    pub id_type: ReferenceIdType,
    pub authority: Option<String>,
    pub field: String,
    pub match_weight: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum ReferenceIdType {
    External,
    Internal,
}

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

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum Cardinality {
    OneToOne,
    OneToMany,
    ManyToOne,
    ManyToMany,
}

impl IdentityPlan {
    pub fn compute_hash(&self) -> String {
        let json = serde_json::to_string(self).expect("Failed to serialize plan");
        let mut hasher = Sha256::new();
        hasher.update(json.as_bytes());
        let result = hasher.finalize();
        hex::encode(result)
    }
}
