//! Property tests for bidirectional relationship resolution

use proptest::prelude::*;

use crate::{
    resolve::links::resolve_relationships,
    syntax::ast::*,
};

// ===== Generators =====

fn valid_ident() -> impl Strategy<Value = String> {
    "[a-zA-Z_][a-zA-Z0-9_]{0,15}".prop_filter("not a keyword", |s| {
        !matches!(
            s.as_str(),
            "use" |
                "character" |
                "template" |
                "life_arc" |
                "schedule" |
                "behavior" |
                "institution" |
                "relationship" |
                "location" |
                "species" |
                "enum" |
                "state" |
                "on" |
                "as" |
                "self" |
                "other" |
                "remove" |
                "append" |
                "forall" |
                "exists" |
                "in" |
                "where" |
                "and" |
                "or" |
                "not" |
                "is" |
                "true" |
                "false"
        )
    })
}

fn valid_field() -> impl Strategy<Value = Field> {
    (valid_ident(), 0i64..100).prop_map(|(name, value)| Field {
        name,
        value: Value::Number(value),
        span: Span::new(0, 10),
    })
}

fn valid_field_list() -> impl Strategy<Value = Vec<Field>> {
    prop::collection::vec(valid_field(), 0..5)
        // Ensure unique field names
        .prop_map(|fields| {
            let mut unique_fields = Vec::new();
            let mut seen_names = std::collections::HashSet::new();

            for field in fields {
                if seen_names.insert(field.name.clone()) {
                    unique_fields.push(field);
                }
            }
            unique_fields
        })
}

fn valid_participant(name: String) -> impl Strategy<Value = Participant> {
    prop::option::of(valid_ident()).prop_map(move |role| Participant {
        name: vec![name.clone()],
        role,
        fields: vec![],
        span: Span::new(0, 10),
    })
}

#[allow(dead_code)]
fn valid_participant_with_blocks(name: String) -> impl Strategy<Value = Participant> {
    (prop::option::of(valid_ident()), valid_field_list()).prop_map(move |(role, fields)| {
        Participant {
            name: vec![name.clone()],
            role,
            fields,
            span: Span::new(0, 10),
        }
    })
}

fn valid_relationship() -> impl Strategy<Value = Relationship> {
    (
        valid_ident(),
        valid_ident(),
        valid_ident(),
        valid_field_list(),
    )
        .prop_flat_map(|(rel_name, person1, person2, fields)| {
            (
                Just(rel_name),
                valid_participant(person1.clone()),
                valid_participant(person2.clone()),
                Just(fields),
            )
        })
        .prop_map(|(name, p1, p2, fields)| Relationship {
            name,
            participants: vec![p1, p2],
            fields,
            span: Span::new(0, 10),
        })
}

fn valid_bidirectional_relationship() -> impl Strategy<Value = (Relationship, Relationship)> {
    (
        valid_ident(),
        valid_ident(),
        valid_ident(),
        valid_field_list(),
        valid_field_list(),
    )
        .prop_flat_map(|(rel_name, person1, person2, shared_fields, self_fields)| {
            let self_fields_clone = self_fields.clone();
            (
                Just(rel_name.clone()),
                Just(person1.clone()),
                Just(person2.clone()),
                Just(shared_fields.clone()),
                Just(self_fields),
                Just(self_fields_clone),
            )
        })
        .prop_map(|(name, p1_name, p2_name, shared, p1_self, p2_self)| {
            // First declaration from p1's perspective
            let p1 = Participant {
                name: vec![p1_name.clone()],
                role: None,
                fields: p1_self,
                span: Span::new(0, 10),
            };
            let p2_in_p1_rel = Participant {
                name: vec![p2_name.clone()],
                role: None,
                fields: vec![],
                span: Span::new(0, 10),
            };

            let rel1 = Relationship {
                name: name.clone(),
                participants: vec![p1, p2_in_p1_rel],
                fields: shared.clone(),
                span: Span::new(0, 10),
            };

            // Second declaration from p2's perspective
            let p2 = Participant {
                name: vec![p2_name],
                role: None,
                fields: p2_self,
                span: Span::new(20, 30),
            };
            let p1_in_p2_rel = Participant {
                name: vec![p1_name],
                role: None,
                fields: vec![],
                span: Span::new(20, 30),
            };

            let rel2 = Relationship {
                name,
                participants: vec![p2, p1_in_p2_rel],
                fields: shared,
                span: Span::new(20, 30),
            };

            (rel1, rel2)
        })
}

// ===== Property Tests =====

proptest! {
    #[test]
    fn test_single_relationship_always_resolves(rel in valid_relationship()) {
        let file = File {
            declarations: vec![Declaration::Relationship(rel)],
        };

        let result = resolve_relationships(&file);
        assert!(result.is_ok(), "Single relationship should always resolve");

        let resolved = result.unwrap();
        assert_eq!(resolved.len(), 1);
    }

    #[test]
    fn test_relationship_participant_count_preserved(rel in valid_relationship()) {
        let file = File {
            declarations: vec![Declaration::Relationship(rel.clone())],
        };

        let resolved = resolve_relationships(&file).unwrap();
        assert_eq!(resolved[0].participants.len(), rel.participants.len());
    }

    #[test]
    fn test_relationship_fields_preserved(rel in valid_relationship()) {
        let file = File {
            declarations: vec![Declaration::Relationship(rel.clone())],
        };

        let resolved = resolve_relationships(&file).unwrap();
        assert_eq!(resolved[0].fields.len(), rel.fields.len());
    }

    #[test]
    fn test_bidirectional_relationships_merge(
        (rel1, rel2) in valid_bidirectional_relationship()
    ) {
        let file = File {
            declarations: vec![
                Declaration::Relationship(rel1),
                Declaration::Relationship(rel2),
            ],
        };

        let result = resolve_relationships(&file);
        assert!(result.is_ok(), "Bidirectional relationships should merge successfully");

        let resolved = result.unwrap();
        // Should merge into single relationship
        assert_eq!(resolved.len(), 1);
    }

    #[test]
    fn test_participant_order_doesnt_matter(
        name in valid_ident(),
        p1 in valid_ident(),
        p2 in valid_ident(),
        fields in valid_field_list()
    ) {
        // Create two identical relationships with participants in different order
        let rel1 = Relationship {
            name: name.clone(),
            participants: vec![
                Participant {
                    name: vec![p1.clone()],
                    role: None,
                    fields: vec![],
                    span: Span::new(0, 10),
                },
                Participant {
                    name: vec![p2.clone()],
                    role: None,
                    fields: vec![],
                    span: Span::new(0, 10),
                },
            ],
            fields: fields.clone(),
            span: Span::new(0, 10),
        };

        let rel2 = Relationship {
            name: name.clone(),
            participants: vec![
                Participant {
                    name: vec![p2.clone()],
                    role: None,
                    fields: vec![],
                    span: Span::new(20, 30),
                },
                Participant {
                    name: vec![p1.clone()],
                    role: None,
                    fields: vec![],
                    span: Span::new(20, 30),
                },
            ],
            fields,
            span: Span::new(20, 30),
        };

        let file = File {
            declarations: vec![
                Declaration::Relationship(rel1),
                Declaration::Relationship(rel2),
            ],
        };

        let result = resolve_relationships(&file);
        assert!(result.is_ok());

        let resolved = result.unwrap();
        // Should recognize as same relationship despite order
        assert_eq!(resolved.len(), 1);
    }

    #[test]
    fn test_different_relationships_stay_separate(
        name1 in valid_ident(),
        name2 in valid_ident(),
        p1 in valid_ident(),
        p2 in valid_ident()
    ) {
        // Skip if names are the same
        if name1 == name2 {
            return Ok(());
        }
        let rel1 = Relationship {
            name: name1,
            participants: vec![
                Participant {
                    name: vec![p1.clone()],
                    role: None,
                    fields: vec![],
                    span: Span::new(0, 10),
                },
                Participant {
                    name: vec![p2.clone()],
                    role: None,
                    fields: vec![],
                    span: Span::new(0, 10),
                },
            ],
            fields: vec![],
            span: Span::new(0, 10),
        };

        let rel2 = Relationship {
            name: name2,
            participants: vec![
                Participant {
                    name: vec![p1],
                    role: None,
                    fields: vec![],
                    span: Span::new(20, 30),
                },
                Participant {
                    name: vec![p2],
                    role: None,
                    fields: vec![],
                    span: Span::new(20, 30),
                },
            ],
            fields: vec![],
            span: Span::new(20, 30),
        };

        let file = File {
            declarations: vec![
                Declaration::Relationship(rel1),
                Declaration::Relationship(rel2),
            ],
        };

        let result = resolve_relationships(&file);
        assert!(result.is_ok());

        let resolved = result.unwrap();
        // Different relationship names should stay separate
        assert_eq!(resolved.len(), 2);
    }

    #[test]
    fn test_self_blocks_are_merged(
        name in valid_ident(),
        p1 in valid_ident(),
        p2 in valid_ident(),
        fields1 in valid_field_list(),
        fields2 in valid_field_list()
    ) {
        let participant1 = Participant {
            name: vec![p1.clone()],
            role: None,
            fields: fields1,
            span: Span::new(0, 10),
        };

        let participant1_again = Participant {
            name: vec![p1.clone()],
            role: None,
            fields: fields2,
            span: Span::new(20, 30),
        };

        let rel1 = Relationship {
            name: name.clone(),
            participants: vec![
                participant1,
                Participant {
                    name: vec![p2.clone()],
                    role: None,
                    fields: vec![],
                    span: Span::new(0, 10),
                },
            ],
            fields: vec![],
            span: Span::new(0, 10),
        };

        let rel2 = Relationship {
            name: name.clone(),
            participants: vec![
                participant1_again,
                Participant {
                    name: vec![p2],
                    role: None,
                    fields: vec![],
                    span: Span::new(20, 30),
                },
            ],
            fields: vec![],
            span: Span::new(20, 30),
        };

        let file = File {
            declarations: vec![
                Declaration::Relationship(rel1),
                Declaration::Relationship(rel2),
            ],
        };

        let result = resolve_relationships(&file);
        // Should succeed unless there are conflicting field values
        // (which is tested separately)
        if result.is_ok() {
            let resolved = result.unwrap();
            assert_eq!(resolved.len(), 1);
        }
    }

    #[test]
    fn test_empty_file_gives_empty_result(
        decls in prop::collection::vec(
            prop_oneof![
                valid_ident().prop_map(|name| Declaration::Character(Character {
                    name,
                    species: None,
                    fields: vec![],
                    template: None,
        uses_behaviors: None,
        uses_schedule: None,
                    span: Span::new(0, 10),
                })),
                valid_ident().prop_map(|name| Declaration::Template(Template {
                    name,
                    species_base: None,
                    fields: vec![],
                    strict: false,
                    includes: vec![],
                    uses_behaviors: None,
                    uses_schedule: None,
                    span: Span::new(0, 10),
                })),
            ],
            0..5
        )
    ) {
        // File with no relationships
        let file = File { declarations: decls };

        let result = resolve_relationships(&file);
        assert!(result.is_ok());

        let resolved = result.unwrap();
        assert_eq!(resolved.len(), 0);
    }
}
