use cannon_common::ir::RelateDirection;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;

// ---------------------------------------------------------------------------
// Semantic resolution types (typed resolution graph)
// ---------------------------------------------------------------------------

/// The final resolution decision after semantic constraint application.
/// Unlike `Decision` (which is the raw scoring output), this incorporates
/// subtype inference and constraint matrix logic.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ResolutionDecision {
    Merge,
    Relate {
        link_type: String,
        /// Direction of the relationship (A->B, B->A, or symmetric).
        /// Defaults to Symmetric for backwards compatibility with old data.
        #[serde(default = "default_relate_direction")]
        direction: RelateDirection,
    },
    NoMatch,
    Review,
}

fn default_relate_direction() -> RelateDirection {
    RelateDirection::Symmetric
}

/// Result of subtype inference on a single entity.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SubtypeInference {
    pub subtype: String,
    pub confidence: f64,
    pub evidence: Vec<String>,
}

/// A cluster of entities with aggregate confidence metrics.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClusterWithConfidence {
    pub members: Vec<Uuid>,
    pub avg_confidence: f64,
    pub min_confidence: f64,
    pub max_confidence: f64,
    pub pair_count: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NormalizedEntity {
    pub id: Uuid,
    pub tenant_id: Uuid,
    pub external_id: String,
    pub entity_type: String,
    pub data: HashMap<String, String>,
    pub source_name: String,
    pub valid_from: Option<chrono::DateTime<chrono::Utc>>,
    pub valid_to: Option<chrono::DateTime<chrono::Utc>>,
    pub last_updated: chrono::DateTime<chrono::Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Decision {
    Merge,
    Review,
    NoMerge,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuleResult {
    pub rule_name: String,
    pub score: f64,
    pub weight: f64,
    pub matched: bool,
    pub explanation: String,
}

#[derive(Debug, Clone, Serialize)]
#[serde(into = "MatchDecisionSerde")]
pub struct MatchDecision {
    pub entity_a_id: Uuid,
    pub entity_b_id: Uuid,
    pub decision: Decision,
    pub confidence: f64,
    /// P(match|data) - posterior probability derived from log-likelihood (FS only)
    pub posterior_probability: Option<f64>,
    pub rule_results: Vec<RuleResult>,
    pub matched_on: Vec<String>,
    pub blocking_key: Option<String>,
    pub temporal_adjustment: Option<f64>,
    /// Inferred subtype for entity A (if semantic engine is active)
    pub subtype_a: Option<SubtypeInference>,
    /// Inferred subtype for entity B (if semantic engine is active)
    pub subtype_b: Option<SubtypeInference>,
    /// Final resolution after semantic constraints. Derived from Decision when
    /// absent in serialized data (backwards compatibility with pre-semantic data).
    pub resolution: ResolutionDecision,
}

/// Helper struct for serde round-tripping of MatchDecision.
/// When `resolution` is absent in the serialized form, it is derived from
/// the `decision` field rather than defaulting to NoMatch.
#[derive(Serialize, Deserialize)]
struct MatchDecisionSerde {
    entity_a_id: Uuid,
    entity_b_id: Uuid,
    decision: Decision,
    confidence: f64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    posterior_probability: Option<f64>,
    rule_results: Vec<RuleResult>,
    matched_on: Vec<String>,
    blocking_key: Option<String>,
    temporal_adjustment: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    subtype_a: Option<SubtypeInference>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    subtype_b: Option<SubtypeInference>,
    #[serde(default)]
    resolution: Option<ResolutionDecision>,
}

impl From<MatchDecisionSerde> for MatchDecision {
    fn from(raw: MatchDecisionSerde) -> Self {
        let resolution = raw.resolution.unwrap_or(
            // Derive from decision when absent (old serialized data)
            match raw.decision {
                Decision::Merge => ResolutionDecision::Merge,
                Decision::Review => ResolutionDecision::Review,
                Decision::NoMerge => ResolutionDecision::NoMatch,
            }
        );
        MatchDecision {
            entity_a_id: raw.entity_a_id,
            entity_b_id: raw.entity_b_id,
            decision: raw.decision,
            confidence: raw.confidence,
            posterior_probability: raw.posterior_probability,
            rule_results: raw.rule_results,
            matched_on: raw.matched_on,
            blocking_key: raw.blocking_key,
            temporal_adjustment: raw.temporal_adjustment,
            subtype_a: raw.subtype_a,
            subtype_b: raw.subtype_b,
            resolution,
        }
    }
}

impl From<MatchDecision> for MatchDecisionSerde {
    fn from(md: MatchDecision) -> Self {
        MatchDecisionSerde {
            entity_a_id: md.entity_a_id,
            entity_b_id: md.entity_b_id,
            decision: md.decision,
            confidence: md.confidence,
            posterior_probability: md.posterior_probability,
            rule_results: md.rule_results,
            matched_on: md.matched_on,
            blocking_key: md.blocking_key,
            temporal_adjustment: md.temporal_adjustment,
            subtype_a: md.subtype_a,
            subtype_b: md.subtype_b,
            resolution: Some(md.resolution),
        }
    }
}

impl<'de> serde::Deserialize<'de> for MatchDecision {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let raw = MatchDecisionSerde::deserialize(deserializer)?;
        Ok(raw.into())
    }
}

// Product relationship classification types (ProductRelationship, ClassifiedRelationship,
// ProductSubtype, HierarchyNode, ProductHierarchy) were removed. The semantic resolution
// engine (semantic.rs) now handles subtype inference and relationship classification
// via spec-driven constraint matrices, producing generic ResolutionDecision::Relate
// edges with link_type + direction metadata.

#[cfg(test)]
mod tests {
    use super::*;
    use cannon_common::ir::RelateDirection;

    // ---------------------------------------------------------------
    // MatchDecision serde round-trip: backwards compatibility
    // ---------------------------------------------------------------

    #[test]
    fn test_deserialize_old_data_without_resolution_merge() {
        // Old serialized data has decision but no resolution field.
        // Should derive resolution from decision.
        let json = r#"{
            "entity_a_id": "00000000-0000-0000-0000-000000000001",
            "entity_b_id": "00000000-0000-0000-0000-000000000002",
            "decision": "merge",
            "confidence": 0.95,
            "rule_results": [],
            "matched_on": [],
            "blocking_key": null,
            "temporal_adjustment": null
        }"#;
        let md: MatchDecision = serde_json::from_str(json).unwrap();
        assert_eq!(md.decision, Decision::Merge);
        assert_eq!(md.resolution, ResolutionDecision::Merge);
        assert!(md.subtype_a.is_none());
        assert!(md.subtype_b.is_none());
    }

    #[test]
    fn test_deserialize_old_data_without_resolution_review() {
        let json = r#"{
            "entity_a_id": "00000000-0000-0000-0000-000000000001",
            "entity_b_id": "00000000-0000-0000-0000-000000000002",
            "decision": "review",
            "confidence": 0.6,
            "rule_results": [],
            "matched_on": [],
            "blocking_key": null,
            "temporal_adjustment": null
        }"#;
        let md: MatchDecision = serde_json::from_str(json).unwrap();
        assert_eq!(md.decision, Decision::Review);
        assert_eq!(md.resolution, ResolutionDecision::Review);
    }

    #[test]
    fn test_deserialize_old_data_without_resolution_nomerge() {
        let json = r#"{
            "entity_a_id": "00000000-0000-0000-0000-000000000001",
            "entity_b_id": "00000000-0000-0000-0000-000000000002",
            "decision": "no_merge",
            "confidence": 0.2,
            "rule_results": [],
            "matched_on": [],
            "blocking_key": null,
            "temporal_adjustment": null
        }"#;
        let md: MatchDecision = serde_json::from_str(json).unwrap();
        assert_eq!(md.decision, Decision::NoMerge);
        assert_eq!(md.resolution, ResolutionDecision::NoMatch);
    }

    #[test]
    fn test_explicit_resolution_takes_precedence() {
        // When resolution is present in JSON, it takes precedence over decision.
        let json = r#"{
            "entity_a_id": "00000000-0000-0000-0000-000000000001",
            "entity_b_id": "00000000-0000-0000-0000-000000000002",
            "decision": "merge",
            "confidence": 0.95,
            "rule_results": [],
            "matched_on": [],
            "blocking_key": null,
            "temporal_adjustment": null,
            "resolution": {"relate": {"link_type": "variant_of", "direction": "b_to_a"}}
        }"#;
        let md: MatchDecision = serde_json::from_str(json).unwrap();
        assert_eq!(md.decision, Decision::Merge);
        assert!(matches!(
            md.resolution,
            ResolutionDecision::Relate { ref link_type, ref direction }
                if link_type == "variant_of" && *direction == RelateDirection::BToA
        ));
    }

    #[test]
    fn test_serde_round_trip_with_relate_and_subtypes() {
        let md = MatchDecision {
            entity_a_id: Uuid::nil(),
            entity_b_id: Uuid::nil(),
            decision: Decision::Merge,
            confidence: 0.92,
            posterior_probability: Some(0.88),
            rule_results: vec![],
            matched_on: vec!["brand".to_string()],
            blocking_key: Some("samsung".to_string()),
            temporal_adjustment: Some(1.0),
            subtype_a: Some(SubtypeInference {
                subtype: "product_model".to_string(),
                confidence: 0.9,
                evidence: vec!["brand:samsung".to_string()],
            }),
            subtype_b: Some(SubtypeInference {
                subtype: "product_variant".to_string(),
                confidence: 0.85,
                evidence: vec!["color:blue".to_string()],
            }),
            resolution: ResolutionDecision::Relate {
                link_type: "variant_of".to_string(),
                direction: RelateDirection::BToA,
            },
        };

        let json = serde_json::to_string(&md).unwrap();
        let deserialized: MatchDecision = serde_json::from_str(&json).unwrap();

        assert_eq!(deserialized.confidence, 0.92);
        assert_eq!(deserialized.posterior_probability, Some(0.88));
        assert_eq!(deserialized.subtype_a.as_ref().unwrap().subtype, "product_model");
        assert_eq!(deserialized.subtype_b.as_ref().unwrap().subtype, "product_variant");
        assert!(matches!(
            deserialized.resolution,
            ResolutionDecision::Relate { ref link_type, ref direction }
                if link_type == "variant_of" && *direction == RelateDirection::BToA
        ));
    }

    #[test]
    fn test_serde_round_trip_merge_no_subtypes() {
        let md = MatchDecision {
            entity_a_id: Uuid::nil(),
            entity_b_id: Uuid::nil(),
            decision: Decision::Merge,
            confidence: 0.95,
            posterior_probability: None,
            rule_results: vec![],
            matched_on: vec![],
            blocking_key: None,
            temporal_adjustment: None,
            subtype_a: None,
            subtype_b: None,
            resolution: ResolutionDecision::Merge,
        };

        let json = serde_json::to_string(&md).unwrap();
        // Verify None fields are skipped
        assert!(!json.contains("posterior_probability"));
        assert!(!json.contains("subtype_a"));
        assert!(!json.contains("subtype_b"));

        let deserialized: MatchDecision = serde_json::from_str(&json).unwrap();
        assert_eq!(deserialized.resolution, ResolutionDecision::Merge);
        assert!(deserialized.subtype_a.is_none());
    }

    #[test]
    fn test_relate_default_direction_symmetric() {
        // When direction is not specified in serialized Relate, defaults to Symmetric
        let json = r#"{
            "entity_a_id": "00000000-0000-0000-0000-000000000001",
            "entity_b_id": "00000000-0000-0000-0000-000000000002",
            "decision": "merge",
            "confidence": 0.9,
            "rule_results": [],
            "matched_on": [],
            "blocking_key": null,
            "temporal_adjustment": null,
            "resolution": {"relate": {"link_type": "related_to"}}
        }"#;
        let md: MatchDecision = serde_json::from_str(json).unwrap();
        assert!(matches!(
            md.resolution,
            ResolutionDecision::Relate { ref link_type, ref direction }
                if link_type == "related_to" && *direction == RelateDirection::Symmetric
        ));
    }
}
