use std::collections::BTreeMap;

use serde_json::json;
use sk_core::klabel;
use sk_core::prelude::*;
use sk_store::TraceEvent;

use super::*;

#[rstest]
#[case::remove_implicit_match_star(
    "remove(status);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_match_star(
    "remove(*, status);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_quoted_label_match(
    "remove(metadata.labels.\"simkube.dev/foo\" == \"bar\", status);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/foo" => "bar"),
                ..Default::default()
            },
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/foo" => "bar"),
                ..Default::default()
            },
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_exists(
    "remove(exists(metadata.labels.\"simkube.dev/foo\"), status);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/foo" => "bar"),
                ..Default::default()
            },
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/foo" => "bar"),
                ..Default::default()
            },
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_not_exists(
    "remove(!exists(metadata.labels.\"simkube.dev/foo\"), status);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/foo" => "bar"),
                ..Default::default()
            },
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/foo" => "bar"),
                ..Default::default()
            },
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_multi_conditional_1(
    "remove(@t >= 123 && metadata.namespace == \"baz\", status);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                namespace: Some("baz".into()),
                ..Default::default()
            },
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                namespace: Some("baz".into()),
                ..Default::default()
            },
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_multi_conditional_2(
    "remove(@t < 123 && metadata.namespace == \"baz\", status);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                namespace: Some("baz".into()),
                ..Default::default()
            },
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({"status": {"foo": "bar"}}),
        },
        DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                namespace: Some("baz".into()),
                ..Default::default()
            },
            data: json!({"status": {"foo": "bar"}}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_wildcards(
    "remove(spec.template.spec.containers[*]);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {
                                    "env": [
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET"}},
                                        {"FOO": "BAR"},
                                    ],
                                },
                                {"env": [{"ASDF": "QWERTY"}]},
                                {
                                    "env": [
                                        {"FOO": "BAR"},
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET"}},
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET_2"}},
                                    ],
                                },
                            ],
                        },
                    },
                },
                "status": {"foo": "bar"},
            }),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {"template": {"spec": {"containers": []}}},
                "status": {"foo": "bar"},
            }),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_variable_def_1(
    "remove($x := spec.template.spec.containers[*].env[*] | exists($x.valueFrom.secretKeyRef), $x);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {
                                    "env": [
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET"}},
                                        {"FOO": "BAR"},
                                    ],
                                },
                                {"env": [{"ASDF": "QWERTY"}]},
                                {
                                    "env": [
                                        {"FOO": "BAR"},
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET"}},
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET_2"}},
                                    ],
                                },
                            ],
                        },
                    },
                },
                "status": {"foo": "bar"},
            }),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {"env": [{"FOO": "BAR"}]},
                                {"env": [{"ASDF": "QWERTY"}]},
                                {"env": [{"FOO": "BAR"}]},
                            ],
                        },
                    },
                },
                "status": {"foo": "bar"},
            }),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_variable_def_2(
    "remove($x := spec.template.spec.containers[*].env[*] | exists($x.valueFrom.secretKeyRef), $x.valueFrom);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {
                                    "env": [
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET"}, "valueTo": 42},
                                        {"FOO": "BAR"},
                                    ],
                                },
                                {"env": [{"ASDF": "QWERTY"}]},
                                {
                                    "env": [
                                        {"FOO": "BAR"},
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET"}, "valueTo": 67},
                                        {"valueFrom": {"secretKeyRef": "SOME_SECRET_2"}, "valueTo": 99},
                                    ],
                                },
                            ],
                        },
                    },
                },
                "status": {"foo": "bar"},
            }),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {"env": [{"valueTo": 42}, {"FOO": "BAR"}]},
                                {"env": [{"ASDF": "QWERTY"}]},
                                {"env": [{"FOO": "BAR"}, {"valueTo": 67}, {"valueTo": 99}]},
                            ],
                        },
                    },
                },
                "status": {"foo": "bar"},
            }),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_variable_deref(
    "remove($x := spec.template.spec.containers[*].image | $x == \"asdf\"
        && metadata.labels.image == $x,
      metadata.labels.image);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("image" => "asdf"),
                ..Default::default()
            },
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [{"image": "asdf"}],
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: Some(BTreeMap::new()),
                ..Default::default()
            },
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [{"image": "asdf"}],
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
)]
#[case::remove_variable_deref_other_variable(
    "remove($x := spec.template.spec.volumes[*] | exists($x.secret)
        && $y := spec.template.spec.containers[*].volumeMounts[*] | $y.name == $x.name,
      $y);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {
                                    "volumeMounts": [
                                        {"name": "empty", "mountPath": "/foo/bar"},
                                        {"name": "secret", "mountPath": "/etc/shadow"},
                                    ],
                                },
                                {"volumeMounts": [{"name": "empty2", "mountPath": "/baz/buz"}]},
                                {
                                    "volumeMounts": [
                                        {"name": "empty", "mountPath": "/foo/bar"},
                                        {"name": "secret", "mountPath": "/etc/passwd"},
                                        {"name": "secret2", "mountPath": "/etc/shadow"},
                                    ],
                                },
                            ],
                            "volumes": [
                                {"name": "empty", "emptyDir": {}},
                                {"name": "empty2", "emptyDir": {}},
                                {"name": "secret", "secret": {"secretName": "SOOPER_SECRET"}},
                                {"name": "secret2", "secret": {"secretName": "SOOPER_SECRET_2"}},
                            ],
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {"volumeMounts": [{"name": "empty", "mountPath": "/foo/bar"}]},
                                {"volumeMounts": [{"name": "empty2", "mountPath": "/baz/buz"}]},
                                {"volumeMounts": [{"name": "empty", "mountPath": "/foo/bar"}]},
                            ],
                            "volumes": [
                                {"name": "empty", "emptyDir": {}},
                                {"name": "empty2", "emptyDir": {}},
                                {"name": "secret", "secret": {"secretName": "SOOPER_SECRET"}},
                                {"name": "secret2", "secret": {"secretName": "SOOPER_SECRET_2"}},
                            ],
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
)]
fn test_remove_command(#[case] cmd_str: &str, #[case] evt: TraceEvent, #[case] expected: TraceEvent) {
    let mut skel = SkelParser::parse(Rule::skel, &cmd_str).unwrap();
    let cmd = parse_command(skel.next().unwrap(), 1234).unwrap();
    let res = process_event(&cmd, evt).unwrap();
    assert_eq!(res, expected);
}

// We don't need to test all the same "match" selectors here, because most of these are covered by
// the remove cases above.  Just add the ones that are specific to modify semantics.
#[rstest]
#[case::modify_implicit_match_star(
    "modify(spec.template.spec.nodeSelector.\"simkube.dev/foo\" = \"bar\");",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "nodeSelector": {
                                "simkube.dev/foo": "bar",
                            },
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
)]
#[case::modify_subst_key(
    "modify($x := metadata.labels.\"simkube.dev/key\" | exists($x), $x = \"baz\");",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/key" => "bar", "other_key" => "something_else"),
                ..Default::default()
            },
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/key" => "baz", "other_key" => "something_else"),
                ..Default::default()
            },
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
)]
#[case::modify_subst_value(
    "modify($x := metadata.labels.\"simkube.dev/key\" | exists($x),
        spec.template.spec.nodeSelector.\"simkube.dev/foo\" = $x);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({}),
        }, DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/key" => "bar"),
                ..Default::default()
            },
            data: json!({}),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: Default::default(),
            data: json!({}),
        }, DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/key" => "bar"),
                ..Default::default()
            },
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "nodeSelector": {
                                "simkube.dev/foo": "bar",
                            },
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
)]
#[case::modify_subst_key_value(
    "modify($x := metadata.labels.\"simkube.dev/key\" | exists($x)
            && $y := spec.template.spec.containers[*] | $y.args[0] == \"PLACEHOLDER\",
       $y.args[0] = $x);",
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/key" => "bar", "other_key" => "something_else"),
                ..Default::default()
            },
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {
                                    "args": ["PLACEHOLDER", "--foo", "--bar"],
                                },
                                {
                                    "args": ["doit", "-x", "-y"],
                                },
                            ]
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
    TraceEvent{
        ts: 1234,
        applied_objs: vec![DynamicObject{
            types: None,
            metadata: metav1::ObjectMeta{
                labels: klabel!("simkube.dev/key" => "bar", "other_key" => "something_else"),
                ..Default::default()
            },
            data: json!({
                "spec": {
                    "template": {
                        "spec": {
                            "containers": [
                                {
                                    "args": ["bar", "--foo", "--bar"],
                                },
                                {
                                    "args": ["doit", "-x", "-y"],
                                },
                            ]
                        },
                    },
                },
            }),
        }],
        deleted_objs: vec![],
    },
)]
fn test_modify_command(#[case] cmd_str: &str, #[case] evt: TraceEvent, #[case] expected: TraceEvent) {
    let mut skel = SkelParser::parse(Rule::skel, &cmd_str).unwrap();
    let cmd = parse_command(skel.next().unwrap(), 1234).unwrap();
    let res = process_event(&cmd, evt).unwrap();
    assert_eq!(res, expected);
}
