use serde_json::{Value, json};

use qa_spec::spec::form::FormSpec;
use qa_spec::spec::question::{ListSpec, QuestionSpec, QuestionType};
use qa_spec::spec::validation::CrossFieldValidation;
use qa_spec::{
    Expr, VisibilityMap, VisibilityMode, answers_schema, apply_computed_answers, example_answers,
    resolve_visibility, validate,
};

fn channel_field() -> QuestionSpec {
    QuestionSpec {
        id: "name".into(),
        kind: QuestionType::String,
        title: "Channel name".into(),
        title_i18n: None,
        description: None,
        description_i18n: None,
        required: true,
        choices: None,
        default_value: None,
        secret: false,
        visible_if: None,
        constraint: None,
        list: None,
        computed: None,
        policy: Default::default(),
        computed_overridable: false,
    }
}

fn build_channel_form(min_items: Option<usize>, max_items: Option<usize>) -> FormSpec {
    FormSpec {
        id: "channels".into(),
        title: "Channels".into(),
        version: "1.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![],
        includes: vec![],
        questions: vec![QuestionSpec {
            id: "channels".into(),
            kind: QuestionType::List,
            title: "Channels".into(),
            title_i18n: None,
            description: None,
            description_i18n: None,
            required: false,
            choices: None,
            default_value: None,
            secret: false,
            visible_if: None,
            constraint: None,
            list: Some(ListSpec {
                min_items,
                max_items,
                fields: vec![channel_field()],
                item_label: None,
            }),
            computed: None,
            policy: Default::default(),
            computed_overridable: false,
        }],
    }
}

fn make_simple_form() -> FormSpec {
    FormSpec {
        id: "simple".into(),
        title: "Simple".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![],
        includes: vec![],
        questions: vec![
            QuestionSpec {
                id: "name".into(),
                kind: QuestionType::String,
                title: "Name".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: true,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                computed: None,
                policy: Default::default(),
                computed_overridable: false,
            },
            QuestionSpec {
                id: "flag".into(),
                kind: QuestionType::Boolean,
                title: "flag".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                computed: None,
                policy: Default::default(),
                computed_overridable: false,
            },
        ],
    }
}

#[test]
fn schema_contains_required_properties() {
    let spec = make_simple_form();
    let visibility = resolve_visibility(&spec, &json!({}), VisibilityMode::Visible);
    let schema = answers_schema(&spec, &visibility);
    let props = schema.get("properties").unwrap().as_object().unwrap();
    assert!(props.contains_key("name"));
    assert!(props.contains_key("flag"));
    let required = schema.get("required").unwrap().as_array().unwrap();
    assert!(required.iter().any(|value| value.as_str() == Some("name")));
}

#[test]
fn example_answers_include_questions() {
    let spec = make_simple_form();
    let visibility = VisibilityMap::from([("name".into(), true), ("flag".into(), true)]);
    let examples = example_answers(&spec, &visibility);
    assert_eq!(examples["name"], Value::String("example-name".into()));
    assert_eq!(examples["flag"], Value::Bool(false));
}

#[test]
fn validation_reports_missing() {
    let spec = make_simple_form();
    let answers: Value = json!({});
    let result = validate(&spec, &answers);
    assert!(!result.valid);
    assert_eq!(result.missing_required, vec!["name"]);
}

#[test]
fn list_validation_respects_bounds() {
    let spec = build_channel_form(Some(1), Some(2));

    let too_few = validate(&spec, &json!({ "channels": [] }));
    assert!(!too_few.valid);
    assert_eq!(too_few.errors[0].code.as_deref(), Some("min_items"));

    let too_many = validate(
        &spec,
        &json!({ "channels": [{ "name": "a" }, { "name": "b" }, { "name": "c" }] }),
    );
    assert!(!too_many.valid);
    assert_eq!(too_many.errors[0].code.as_deref(), Some("max_items"));

    let missing_field = validate(&spec, &json!({ "channels": [{}] }));
    assert!(!missing_field.valid);
    assert_eq!(
        missing_field.errors[0].code.as_deref(),
        Some("missing_field")
    );

    let invalid_type = validate(&spec, &json!({ "channels": [{ "name": 123 }] }));
    assert!(!invalid_type.valid);
    assert_eq!(
        invalid_type.errors[0].code.as_deref(),
        Some("type_mismatch")
    );

    let valid = validate(&spec, &json!({ "channels": [{ "name": "alpha" }] }));
    assert!(valid.valid);
}

#[test]
fn list_schema_includes_items() {
    let spec = build_channel_form(Some(1), None);
    let visibility = VisibilityMap::from([("channels".into(), true)]);
    let schema = answers_schema(&spec, &visibility);
    let props = schema
        .get("properties")
        .and_then(Value::as_object)
        .expect("properties map");
    let channels = props
        .get("channels")
        .and_then(Value::as_object)
        .expect("channels schema");
    assert_eq!(channels["minItems"].as_u64(), Some(1));
    let items = channels["items"]
        .as_object()
        .expect("items schema should be object");
    let item_props = items["properties"]
        .as_object()
        .expect("items should describe properties");
    assert!(item_props.contains_key("name"));
}

#[test]
fn computed_fields_satisfy_required_answers() {
    let spec = FormSpec {
        id: "computed".into(),
        title: "Computed Form".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![],
        includes: vec![],
        questions: vec![
            QuestionSpec {
                id: "name".into(),
                kind: QuestionType::String,
                title: "Name".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: true,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
            QuestionSpec {
                id: "slug".into(),
                kind: QuestionType::String,
                title: "Slug".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: true,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: Some(Expr::Answer {
                    path: "name".into(),
                }),
                computed_overridable: false,
            },
        ],
    };

    let answers = json!({ "name": "Greentic" });
    let computed = apply_computed_answers(&spec, &answers);
    assert_eq!(computed["slug"], "Greentic");

    let result = validate(&spec, &answers);
    assert!(result.valid);
}

#[test]
fn computed_field_overwrites_user_values_when_not_overridable() {
    let spec = FormSpec {
        id: "computed_override".into(),
        title: "Computed Override".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![],
        includes: vec![],
        questions: vec![
            QuestionSpec {
                id: "source".into(),
                kind: QuestionType::String,
                title: "Source".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: true,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
            QuestionSpec {
                id: "derived".into(),
                kind: QuestionType::String,
                title: "Derived".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: true,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: Some(Expr::Answer {
                    path: "source".into(),
                }),
                computed_overridable: false,
            },
        ],
    };

    let answers = json!({
        "source": "Greentic",
        "derived": "custom"
    });
    let computed = apply_computed_answers(&spec, &answers);
    assert_eq!(computed["derived"], "Greentic");
}

#[test]
fn computed_field_respects_overrides_when_allowed() {
    let mut spec = FormSpec {
        id: "computed_override".into(),
        title: "Computed Override".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![],
        includes: vec![],
        questions: Vec::new(),
    };
    spec.questions = vec![
        QuestionSpec {
            id: "source".into(),
            kind: QuestionType::String,
            title: "Source".into(),
            title_i18n: None,
            description: None,
            description_i18n: None,
            required: true,
            choices: None,
            default_value: None,
            secret: false,
            visible_if: None,
            constraint: None,
            list: None,
            policy: Default::default(),
            computed: None,
            computed_overridable: false,
        },
        QuestionSpec {
            id: "derived".into(),
            kind: QuestionType::String,
            title: "Derived".into(),
            title_i18n: None,
            description: None,
            description_i18n: None,
            required: true,
            choices: None,
            default_value: None,
            secret: false,
            visible_if: None,
            constraint: None,
            list: None,
            policy: Default::default(),
            computed: Some(Expr::Answer {
                path: "source".into(),
            }),
            computed_overridable: true,
        },
    ];

    let answers = json!({
        "source": "Greentic",
        "derived": "custom"
    });
    let computed = apply_computed_answers(&spec, &answers);
    assert_eq!(computed["derived"], "custom");
}

#[test]
fn cross_field_validation_fails_when_required_missing() {
    let spec = FormSpec {
        id: "cross".into(),
        title: "Cross Field".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![CrossFieldValidation {
            id: Some("dependent".into()),
            message: "B is required when A is set".into(),
            fields: vec!["b".into()],
            condition: Expr::And {
                expressions: vec![
                    Expr::IsSet { path: "a".into() },
                    Expr::Not {
                        expression: Box::new(Expr::IsSet { path: "b".into() }),
                    },
                ],
            },
            code: Some("missing_dependent".into()),
        }],
        includes: vec![],
        questions: vec![
            QuestionSpec {
                id: "a".into(),
                kind: QuestionType::String,
                title: "A".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
            QuestionSpec {
                id: "b".into(),
                kind: QuestionType::String,
                title: "B".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
        ],
    };

    let result = validate(&spec, &json!({ "a": "value" }));
    assert!(!result.valid);
    assert_eq!(result.errors.len(), 1);
    assert_eq!(result.errors[0].message, "B is required when A is set");

    let valid_result = validate(&spec, &json!({ "a": "value", "b": "present" }));
    assert!(valid_result.valid);
}

#[test]
fn cross_field_validation_requires_at_least_one_contact() {
    let spec = FormSpec {
        id: "contact".into(),
        title: "Contact".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![CrossFieldValidation {
            id: Some("contact".into()),
            message: "Provide email or phone".into(),
            fields: vec!["email".into(), "phone".into()],
            condition: Expr::And {
                expressions: vec![
                    Expr::Not {
                        expression: Box::new(Expr::IsSet {
                            path: "email".into(),
                        }),
                    },
                    Expr::Not {
                        expression: Box::new(Expr::IsSet {
                            path: "phone".into(),
                        }),
                    },
                ],
            },
            code: Some("contact_required".into()),
        }],
        includes: vec![],
        questions: vec![
            QuestionSpec {
                id: "email".into(),
                kind: QuestionType::String,
                title: "Email".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
            QuestionSpec {
                id: "phone".into(),
                kind: QuestionType::String,
                title: "Phone".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
        ],
    };

    let result = validate(&spec, &json!({}));
    assert!(!result.valid);
    assert_eq!(result.errors[0].code.as_deref(), Some("contact_required"));

    let valid_result = validate(&spec, &json!({ "email": "engagement@greentic.ai" }));
    assert!(valid_result.valid);
}

#[test]
fn answer_expression_controls_visibility() {
    let spec = FormSpec {
        id: "visibility".into(),
        title: "Visibility".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![],
        includes: vec![],
        questions: vec![
            QuestionSpec {
                id: "trigger".into(),
                kind: QuestionType::Boolean,
                title: "Trigger".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
            QuestionSpec {
                id: "dependent".into(),
                kind: QuestionType::String,
                title: "Dependent".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: Some(Expr::Answer {
                    path: "trigger".into(),
                }),
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
        ],
    };

    let visible = resolve_visibility(&spec, &json!({ "trigger": true }), VisibilityMode::Visible);
    assert!(visible["dependent"]);

    let hidden = resolve_visibility(&spec, &json!({ "trigger": false }), VisibilityMode::Visible);
    assert!(!hidden["dependent"]);
}

#[test]
fn visibility_not_expression_fires_when_trigger_unset() {
    let spec = FormSpec {
        id: "visibility".into(),
        title: "Visibility".into(),
        version: "1.0.0".into(),
        description: None,
        presentation: None,
        progress_policy: None,
        secrets_policy: None,
        store: vec![],
        validations: vec![],
        includes: vec![],
        questions: vec![
            QuestionSpec {
                id: "flag".into(),
                kind: QuestionType::Boolean,
                title: "Flag".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: None,
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
            QuestionSpec {
                id: "dependent".into(),
                kind: QuestionType::String,
                title: "Dependent".into(),
                title_i18n: None,
                description: None,
                description_i18n: None,
                required: false,
                choices: None,
                default_value: None,
                secret: false,
                visible_if: Some(Expr::Not {
                    expression: Box::new(Expr::IsSet {
                        path: "flag".into(),
                    }),
                }),
                constraint: None,
                list: None,
                policy: Default::default(),
                computed: None,
                computed_overridable: false,
            },
        ],
    };

    let visible = resolve_visibility(&spec, &json!({}), VisibilityMode::Visible);
    assert!(visible["dependent"]);

    let hidden = resolve_visibility(&spec, &json!({ "flag": true }), VisibilityMode::Visible);
    assert!(!hidden["dependent"]);
}
