use cueward_core::{
    ShortcutAction, ShortcutInputPolicy, ShortcutReference, ShortcutSpec, ShortcutSurface,
};
use serde_json::json;

use crate::shortcuts::{
    append_action, compile_actions, compiled_action_count, decompile_actions,
};

#[test]
fn compile_actions_builds_text_and_clipboard_chain() {
    let spec = ShortcutSpec {
        name: "Plan Smoke".into(),
        surfaces: vec![],
        input: ShortcutInputPolicy::Any,
        actions: vec![
            ShortcutAction::Text {
                value: "hello".into(),
                output: Some("greeting".into()),
            },
            ShortcutAction::CopyToClipboard {
                from: ShortcutReference::Output("greeting".into()),
            },
        ],
    };

    let payload = compile_actions(&spec).unwrap();
    let actions = plist::from_bytes::<Vec<plist::Value>>(&payload).unwrap();

    assert_eq!(actions.len(), 2);

    let first = actions[0].as_dictionary().unwrap();
    assert_eq!(
        first.get("WFWorkflowActionIdentifier").unwrap().as_string(),
        Some("is.workflow.actions.gettext")
    );
    let first_params = first.get("WFWorkflowActionParameters").unwrap().as_dictionary().unwrap();
    assert_eq!(
        first_params.get("CustomOutputName").and_then(plist::Value::as_string),
        Some("greeting")
    );

    let second = actions[1].as_dictionary().unwrap();
    assert_eq!(
        second.get("WFWorkflowActionIdentifier").unwrap().as_string(),
        Some("is.workflow.actions.setclipboard")
    );
    let second_params = second.get("WFWorkflowActionParameters").unwrap().as_dictionary().unwrap();
    let input = second_params.get("WFInput").unwrap().as_dictionary().unwrap();
    let value = input.get("Value").unwrap().as_dictionary().unwrap();
    assert_eq!(
        value.get("OutputName").and_then(plist::Value::as_string),
        Some("greeting")
    );
}

#[test]
fn append_action_uses_existing_custom_output_name_as_reference() {
    let spec = ShortcutSpec {
        name: "Plan Smoke".into(),
        surfaces: vec![],
        input: ShortcutInputPolicy::Any,
        actions: vec![ShortcutAction::Text {
            value: "hello".into(),
            output: Some("greeting".into()),
        }],
    };

    let existing = compile_actions(&spec).unwrap();
    let appended = append_action(
        &existing,
        &ShortcutAction::CopyToClipboard {
            from: ShortcutReference::Output("greeting".into()),
        },
    )
    .unwrap();

    let actions = plist::from_bytes::<Vec<plist::Value>>(&appended).unwrap();
    assert_eq!(actions.len(), 2);
    let second = actions[1].as_dictionary().unwrap();
    let second_params = second.get("WFWorkflowActionParameters").unwrap().as_dictionary().unwrap();
    let input = second_params.get("WFInput").unwrap().as_dictionary().unwrap();
    let value = input.get("Value").unwrap().as_dictionary().unwrap();
    assert_eq!(
        value.get("OutputName").and_then(plist::Value::as_string),
        Some("greeting")
    );
}

#[test]
fn append_action_uses_exported_default_output_alias_as_reference() {
    let actions = vec![json!({
        "WFWorkflowActionIdentifier": "is.workflow.actions.gettext",
        "WFWorkflowActionParameters": {
            "UUID": "12345678-1234-1234-1234-1234567890AB",
            "WFTextActionText": "hello"
        }
    })];
    let mut payload = Vec::new();
    plist::to_writer_binary(&mut payload, &actions).unwrap();

    let appended = append_action(
        &payload,
        &ShortcutAction::CopyToClipboard {
            from: ShortcutReference::Output("text".into()),
        },
    )
    .unwrap();

    let actions = plist::from_bytes::<Vec<plist::Value>>(&appended).unwrap();
    let second = actions[1].as_dictionary().unwrap();
    let second_params = second.get("WFWorkflowActionParameters").unwrap().as_dictionary().unwrap();
    let input = second_params.get("WFInput").unwrap().as_dictionary().unwrap();
    let value = input.get("Value").unwrap().as_dictionary().unwrap();
    assert_eq!(
        value.get("OutputName").and_then(plist::Value::as_string),
        Some("Text")
    );
}

#[test]
fn append_action_deduplicates_inferred_default_output_aliases() {
    let actions = vec![
        json!({
            "WFWorkflowActionIdentifier": "is.workflow.actions.gettext",
            "WFWorkflowActionParameters": {
                "UUID": "12345678-1234-1234-1234-1234567890AB",
                "WFTextActionText": "first"
            }
        }),
        json!({
            "WFWorkflowActionIdentifier": "is.workflow.actions.gettext",
            "WFWorkflowActionParameters": {
                "UUID": "22345678-1234-1234-1234-1234567890AB",
                "WFTextActionText": "second"
            }
        }),
    ];
    let mut payload = Vec::new();
    plist::to_writer_binary(&mut payload, &actions).unwrap();

    let appended = append_action(
        &payload,
        &ShortcutAction::CopyToClipboard {
            from: ShortcutReference::Output("text_2".into()),
        },
    )
    .unwrap();

    let actions = plist::from_bytes::<Vec<plist::Value>>(&appended).unwrap();
    let second_uuid = actions[1]
        .as_dictionary()
        .unwrap()
        .get("WFWorkflowActionParameters")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("UUID")
        .and_then(plist::Value::as_string)
        .unwrap()
        .to_string();
    let appended_input_uuid = actions[2]
        .as_dictionary()
        .unwrap()
        .get("WFWorkflowActionParameters")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("WFInput")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("Value")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("OutputUUID")
        .and_then(plist::Value::as_string)
        .unwrap()
        .to_string();

    assert_eq!(appended_input_uuid, second_uuid);
}

#[test]
fn compile_actions_resolves_input_before_reusing_same_output_name() {
    let spec = ShortcutSpec {
        name: "Alias Reuse".into(),
        surfaces: vec![],
        input: ShortcutInputPolicy::Any,
        actions: vec![
            ShortcutAction::Text {
                value: "hello".into(),
                output: Some("url_text".into()),
            },
            ShortcutAction::GetUrls {
                from: ShortcutReference::Output("url_text".into()),
                output: Some("url_text".into()),
            },
        ],
    };

    let payload = compile_actions(&spec).unwrap();
    let actions = plist::from_bytes::<Vec<plist::Value>>(&payload).unwrap();

    let first_uuid = actions[0]
        .as_dictionary()
        .unwrap()
        .get("WFWorkflowActionParameters")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("UUID")
        .and_then(plist::Value::as_string)
        .unwrap()
        .to_string();

    let second_input_uuid = actions[1]
        .as_dictionary()
        .unwrap()
        .get("WFWorkflowActionParameters")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("WFInput")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("Value")
        .unwrap()
        .as_dictionary()
        .unwrap()
        .get("OutputUUID")
        .and_then(plist::Value::as_string)
        .unwrap()
        .to_string();

    assert_eq!(second_input_uuid, first_uuid);
}

#[test]
fn compile_actions_supports_extension_input_as_direct_reference() {
    let spec = ShortcutSpec {
        name: "Extension Input".into(),
        surfaces: vec![],
        input: ShortcutInputPolicy::Url,
        actions: vec![ShortcutAction::GetUrls {
            from: ShortcutReference::ExtensionInput,
            output: Some("urls".into()),
        }],
    };

    let payload = compile_actions(&spec).unwrap();
    let actions = plist::from_bytes::<Vec<plist::Value>>(&payload).unwrap();
    let params = actions[0]
        .as_dictionary()
        .unwrap()
        .get("WFWorkflowActionParameters")
        .unwrap()
        .as_dictionary()
        .unwrap();
    let input = params.get("WFInput").unwrap().as_dictionary().unwrap();
    let value = input.get("Value").unwrap().as_dictionary().unwrap();

    assert_eq!(
        input.get("WFSerializationType").and_then(plist::Value::as_string),
        Some("WFTextTokenAttachment")
    );
    assert_eq!(value.get("Type").and_then(plist::Value::as_string), Some("ExtensionInput"));
}

#[test]
fn decompile_actions_round_trips_supported_clean_url_subset() {
    let spec = ShortcutSpec {
        name: "Clean URL Share".into(),
        surfaces: vec![ShortcutSurface::ShareSheet, ShortcutSurface::LibraryRoot],
        input: ShortcutInputPolicy::Url,
        actions: vec![
            ShortcutAction::GetText {
                from: ShortcutReference::ExtensionInput,
                output: Some("input_url_text".into()),
            },
            ShortcutAction::GetUrls {
                from: ShortcutReference::Output("input_url_text".into()),
                output: Some("urls".into()),
            },
            ShortcutAction::GetText {
                from: ShortcutReference::Output("urls".into()),
                output: Some("normalized_url_text".into()),
            },
            ShortcutAction::ReplaceText {
                from: ShortcutReference::Output("normalized_url_text".into()),
                find: "foo".into(),
                replace: "bar".into(),
                regex: true,
                ignore_case: true,
                output: Some("tracking_removed".into()),
            },
            ShortcutAction::CopyToClipboard {
                from: ShortcutReference::Output("tracking_removed".into()),
            },
            ShortcutAction::Share {
                from: ShortcutReference::Output("tracking_removed".into()),
            },
        ],
    };

    let payload = compile_actions(&spec).unwrap();
    let decompiled = decompile_actions(&payload).unwrap();

    assert_eq!(decompiled, spec.actions);
}

#[test]
fn decompile_actions_preserves_inferred_default_output_aliases() {
    let actions = vec![
        json!({
            "WFWorkflowActionIdentifier": "is.workflow.actions.gettext",
            "WFWorkflowActionParameters": {
                "UUID": "12345678-1234-1234-1234-1234567890AB",
                "WFTextActionText": "hello"
            }
        }),
        json!({
            "WFWorkflowActionIdentifier": "is.workflow.actions.setclipboard",
            "WFWorkflowActionParameters": {
                "WFInput": {
                    "Value": {
                        "OutputName": "Text",
                        "OutputUUID": "12345678-1234-1234-1234-1234567890AB",
                        "Type": "ActionOutput"
                    },
                    "WFSerializationType": "WFTextTokenAttachment"
                }
            }
        }),
    ];
    let mut payload = Vec::new();
    plist::to_writer_binary(&mut payload, &actions).unwrap();

    let decompiled = decompile_actions(&payload).unwrap();

    assert_eq!(
        decompiled,
        vec![
            ShortcutAction::Text {
                value: "hello".into(),
                output: Some("text".into()),
            },
            ShortcutAction::CopyToClipboard {
                from: ShortcutReference::Output("text".into()),
            },
        ]
    );
}

#[test]
fn compile_and_decompile_if_and_repeat_round_trip() {
    let spec = ShortcutSpec {
        name: "Control Flow Smoke".into(),
        surfaces: vec![],
        input: ShortcutInputPolicy::Any,
        actions: vec![
            ShortcutAction::Text {
                value: "match".into(),
                output: Some("input_text".into()),
            },
            ShortcutAction::IfEqualsText {
                input: ShortcutReference::Output("input_text".into()),
                value: "match".into(),
                then_actions: vec![
                    ShortcutAction::Text {
                        value: "ok".into(),
                        output: Some("condition_result".into()),
                    },
                    ShortcutAction::CopyToClipboard {
                        from: ShortcutReference::Output("condition_result".into()),
                    },
                ],
            },
            ShortcutAction::RepeatEach {
                input: ShortcutReference::Output("input_text".into()),
                body: vec![ShortcutAction::Share {
                    from: ShortcutReference::RepeatItem,
                }],
            },
        ],
    };

    let payload = compile_actions(&spec).unwrap();
    let decompiled = decompile_actions(&payload).unwrap();

    assert_eq!(decompiled, spec.actions);
}

#[test]
fn compiled_action_count_flattens_control_flow_wrappers() {
    let spec = ShortcutSpec {
        name: "Control Flow Smoke".into(),
        surfaces: vec![],
        input: ShortcutInputPolicy::Any,
        actions: vec![
            ShortcutAction::Text {
                value: "match".into(),
                output: Some("input_text".into()),
            },
            ShortcutAction::IfEqualsText {
                input: ShortcutReference::Output("input_text".into()),
                value: "match".into(),
                then_actions: vec![
                    ShortcutAction::Text {
                        value: "ok".into(),
                        output: Some("condition_result".into()),
                    },
                    ShortcutAction::CopyToClipboard {
                        from: ShortcutReference::Output("condition_result".into()),
                    },
                ],
            },
            ShortcutAction::RepeatEach {
                input: ShortcutReference::Output("input_text".into()),
                body: vec![ShortcutAction::Share {
                    from: ShortcutReference::RepeatItem,
                }],
            },
        ],
    };

    let payload = compile_actions(&spec).unwrap();

    assert_eq!(compiled_action_count(&payload).unwrap(), 8);
}
