// Copyright 2026 John Wilger

#[cfg(test)]
mod tests {
    use std::error::Error;
    use std::fs::{create_dir, exists, read_to_string, remove_file};
    use std::path::Path;

    use assert_cmd::Command;
    use predicates::prelude::predicate;
    use tempfile::TempDir;

    #[test]
    fn update_workflow_description_rewrites_synchronized_artifacts() -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--description",
                "Actor opens a repair ticket with priority.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success()
            .stdout(predicate::str::contains("updated workflow Open ticket"));

        let lean = read_to_string(temp_dir.path().join("model/lean/OpenTicket.lean"))?;
        let quint = read_to_string(temp_dir.path().join("model/quint/OpenTicket.qnt"))?;

        assert!(
            lean.contains(
                "def workflowDescription := \"Actor opens a repair ticket with priority.\""
            ),
            "Lean artifact must represent the updated workflow description"
        );
        assert!(
            quint.contains(
                "val workflowDescription = \"Actor opens a repair ticket with priority.\""
            ),
            "Quint artifact must represent the updated workflow description"
        );

        Ok(())
    }

    #[test]
    fn update_workflow_name_rewrites_synchronized_artifacts() -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open repair ticket",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success()
            .stdout(predicate::str::contains(
                "updated workflow Open repair ticket",
            ));

        Command::cargo_bin("emc")?
            .arg("check")
            .current_dir(temp_dir.path())
            .assert()
            .success();

        let lean = read_to_string(temp_dir.path().join("model/lean/OpenRepairTicket.lean"))?;
        let quint = read_to_string(temp_dir.path().join("model/quint/OpenRepairTicket.qnt"))?;

        assert!(
            lean.contains("namespace OpenRepairTicket"),
            "Lean artifact must move to the updated workflow module"
        );
        assert!(
            lean.contains("def workflowName := \"Open repair ticket\""),
            "Lean artifact must represent the updated workflow name"
        );
        assert!(
            quint.contains("module OpenRepairTicket {"),
            "Quint artifact must move to the updated workflow module"
        );
        assert!(
            quint.contains("val workflowName = \"Open repair ticket\""),
            "Quint artifact must represent the updated workflow name"
        );
        assert!(
            !exists(temp_dir.path().join("model/lean/OpenTicket.lean"))?,
            "workflow name update must remove the old Lean module"
        );
        assert!(
            !exists(temp_dir.path().join("model/quint/OpenTicket.qnt"))?,
            "workflow name update must remove the old Quint module"
        );

        Ok(())
    }

    #[test]
    fn update_workflow_name_rejects_formal_module_name_collisions() -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-bug",
                "--name",
                "Open bug",
                "--description",
                "Actor opens a bug report.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-bug",
                "--name",
                "Open-ticket",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .failure()
            .stderr(predicate::str::contains(
                "workflow module OpenTicket already exists",
            ));

        let lean = read_to_string(temp_dir.path().join("model/lean/OpenBug.lean"))?;
        let quint = read_to_string(temp_dir.path().join("model/quint/OpenBug.qnt"))?;

        assert!(
            lean.contains("def workflowName := \"Open bug\""),
            "rejected workflow rename must not mutate the Lean artifact"
        );
        assert!(
            quint.contains("val workflowName = \"Open bug\""),
            "rejected workflow rename must not mutate the Quint artifact"
        );

        Ok(())
    }

    #[test]
    fn update_workflow_name_self_heals_missing_formal_source_modules() -> Result<(), Box<dyn Error>>
    {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        // The workflow modules are write-only projections of the event log.
        // Deleting them does not unmodel the workflow: the next command
        // regenerates them from the log, so the rename still succeeds against
        // the authoritative model rather than failing on the missing artifacts.
        remove_file(temp_dir.path().join("model/lean/OpenTicket.lean"))?;
        remove_file(temp_dir.path().join("model/quint/OpenTicket.qnt"))?;

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open repair ticket",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success()
            .stdout(predicate::str::contains(
                "updated workflow Open repair ticket",
            ));

        let lean = read_to_string(temp_dir.path().join("model/lean/OpenRepairTicket.lean"))?;
        assert!(
            lean.contains("def workflowName := \"Open repair ticket\""),
            "the deleted workflow module must be regenerated from the log under the new name"
        );

        Ok(())
    }

    #[test]
    fn update_workflow_name_rejects_stale_formal_module_cleanup_errors()
    -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        let stale_lean_module = temp_dir.path().join("model/lean/OpenTicket.lean");
        remove_file(&stale_lean_module)?;
        create_dir(&stale_lean_module)?;

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open repair ticket",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .failure();

        assert!(
            !exists(temp_dir.path().join("model/lean/OpenRepairTicket.lean"))?,
            "failed stale-module cleanup must not write the new Lean module"
        );

        Ok(())
    }

    #[test]
    fn update_workflow_name_rejects_non_slug_flag() -> Result<(), Box<dyn Error>> {
        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--workflow",
                "open-ticket",
                "--name",
                "Open repair ticket",
            ])
            .assert()
            .failure()
            .stderr(predicate::str::contains(
                "usage: emc <command> [arguments]; run emc --help",
            ));

        Ok(())
    }

    #[test]
    fn update_workflow_name_rejects_non_name_flag() -> Result<(), Box<dyn Error>> {
        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--title",
                "Open repair ticket",
            ])
            .assert()
            .failure()
            .stderr(predicate::str::contains(
                "usage: emc <command> [arguments]; run emc --help",
            ));

        Ok(())
    }

    #[test]
    fn update_workflow_description_preserves_composed_slice_artifacts() -> Result<(), Box<dyn Error>>
    {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "slice",
                "--workflow",
                "open-ticket",
                "--slug",
                "capture-ticket",
                "--name",
                "Capture ticket",
                "--type",
                "state_view",
                "--description",
                "Actor enters repair ticket details.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--description",
                "Actor opens a repair ticket with priority.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success()
            .stdout(predicate::str::contains("updated workflow Open ticket"));

        let lean = read_to_string(temp_dir.path().join("model/lean/OpenTicket.lean"))?;
        let quint = read_to_string(temp_dir.path().join("model/quint/OpenTicket.qnt"))?;

        assert!(
            lean.contains(
                "def workflowSliceDetails : List WorkflowSliceDetail := [{ slug := \"capture-ticket\", name := \"Capture ticket\", kind := SliceKindName.stateView, description := \"Actor enters repair ticket details.\" }]"
            ),
            "Lean update must preserve composed slice details"
        );
        assert!(
            quint.contains(
                "val workflowSliceDetails: List[WorkflowSliceDetail] = [{ slug: \"capture-ticket\", name: \"Capture ticket\", kind: SliceStateView, description: \"Actor enters repair ticket details.\" }]"
            ),
            "Quint update must preserve composed slice details"
        );

        Ok(())
    }

    #[test]
    fn update_workflow_description_preserves_existing_canonical_transitions()
    -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        add_slice(temp_dir.path(), "submit-ticket", "Submit ticket")?;
        add_slice(temp_dir.path(), "review-ticket", "Review ticket")?;

        Command::cargo_bin("emc")?
            .args([
                "connect",
                "workflow",
                "--workflow",
                "open-ticket",
                "--from",
                "submit-ticket",
                "--to",
                "review-ticket",
                "--via",
                "event",
                "--name",
                "TicketSubmittedForReview",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--description",
                "Actor opens a repair ticket with priority.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        let lean = read_to_string(temp_dir.path().join("model/lean/OpenTicket.lean"))?;
        let quint = read_to_string(temp_dir.path().join("model/quint/OpenTicket.qnt"))?;

        assert!(
            lean.contains(
                "def workflowTransitions : List WorkflowTransition := [{ source := \"submit-ticket\", target := \"review-ticket\", kind := WorkflowTransitionKind.event, trigger := \"TicketSubmittedForReview\", sourceControl := \"\", targetView := \"\", rationale := \"\", payloadContract := \"\" }]"
            ),
            "Lean update must preserve existing event transitions"
        );
        assert!(
            quint.contains(
                "val workflowTransitions: List[WorkflowTransition] = [{ source: \"submit-ticket\", target: \"review-ticket\", kind: Event, trigger: \"TicketSubmittedForReview\", sourceControl: \"\", targetView: \"\", rationale: \"\", payloadContract: \"\" }]"
            ),
            "Quint update must preserve existing event transitions"
        );

        Ok(())
    }

    #[test]
    fn update_workflow_description_preserves_existing_workflow_exit_transition()
    -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                "open-ticket",
                "--name",
                "Open ticket",
                "--description",
                "Actor opens a repair ticket.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        add_slice(temp_dir.path(), "capture-ticket", "Capture ticket")?;

        Command::cargo_bin("emc")?
            .args([
                "connect",
                "workflow",
                "--workflow",
                "open-ticket",
                "--from",
                "capture-ticket",
                "--to-workflow",
                "repair-complete",
                "--via",
                "outcome",
                "--name",
                "ticket_closed",
                "--reason",
                "Closed tickets continue to completion.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args([
                "update",
                "workflow",
                "--slug",
                "open-ticket",
                "--description",
                "Actor opens a repair ticket with priority.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        let lean = read_to_string(temp_dir.path().join("model/lean/OpenTicket.lean"))?;
        let quint = read_to_string(temp_dir.path().join("model/quint/OpenTicket.qnt"))?;

        assert!(
            lean.contains(
                "def workflowTransitions : List WorkflowTransition := [{ source := \"capture-ticket\", target := \"repair-complete\", kind := WorkflowTransitionKind.workflowExitOutcome, trigger := \"ticket_closed\", sourceControl := \"\", targetView := \"\", rationale := \"Closed tickets continue to completion.\", payloadContract := \"\" }]"
            ),
            "Lean update must preserve existing workflow exit transitions"
        );
        assert!(
            quint.contains(
                "val workflowTransitions: List[WorkflowTransition] = [{ source: \"capture-ticket\", target: \"repair-complete\", kind: WorkflowExitOutcome, trigger: \"ticket_closed\", sourceControl: \"\", targetView: \"\", rationale: \"Closed tickets continue to completion.\", payloadContract: \"\" }]"
            ),
            "Quint update must preserve existing workflow exit transitions"
        );

        Ok(())
    }

    fn add_slice(cwd: &Path, slug: &str, name: &str) -> Result<(), Box<dyn Error>> {
        Command::cargo_bin("emc")?
            .args([
                "add",
                "slice",
                "--workflow",
                "open-ticket",
                "--slug",
                slug,
                "--name",
                name,
                "--type",
                "state_view",
                "--description",
                "Slice description.",
            ])
            .current_dir(cwd)
            .assert()
            .success();
        Ok(())
    }

    #[test]
    fn remove_workflow_deletes_index_entry_and_owned_artifacts() -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        add_workflow(temp_dir.path(), "open-ticket", "Open ticket")?;
        add_workflow(temp_dir.path(), "close-ticket", "Close ticket")?;
        add_slice_to_workflow(
            temp_dir.path(),
            "open-ticket",
            "capture-request",
            "Capture request",
        )?;

        Command::cargo_bin("emc")?
            .args(["remove", "workflow", "--slug", "open-ticket"])
            .current_dir(temp_dir.path())
            .assert()
            .success()
            .stdout(predicate::str::contains("removed workflow Open ticket"));

        Command::cargo_bin("emc")?
            .arg("check")
            .current_dir(temp_dir.path())
            .assert()
            .success();

        assert!(
            exists(temp_dir.path().join("model/lean/CloseTicket.lean"))?,
            "unrelated workflow Lean module must remain modeled"
        );
        assert!(
            exists(temp_dir.path().join("model/quint/CloseTicket.qnt"))?,
            "unrelated workflow Quint module must remain modeled"
        );
        assert!(
            !exists(temp_dir.path().join("model/lean/OpenTicket.lean"))?,
            "removed workflow Lean module must be deleted"
        );
        assert!(
            !exists(temp_dir.path().join("model/quint/OpenTicket.qnt"))?,
            "removed workflow Quint module must be deleted"
        );
        assert!(
            !exists(
                temp_dir
                    .path()
                    .join("model/lean/slices/CaptureRequest.lean")
            )?,
            "owned slice Lean module must be deleted"
        );
        assert!(
            !exists(
                temp_dir
                    .path()
                    .join("model/quint/slices/CaptureRequest.qnt")
            )?,
            "owned slice Quint module must be deleted"
        );

        Ok(())
    }

    #[test]
    fn remove_workflow_rejects_incoming_workflow_exit_references() -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        add_workflow(temp_dir.path(), "triage-ticket", "Triage ticket")?;
        add_workflow(temp_dir.path(), "close-ticket", "Close ticket")?;
        add_slice_to_workflow(
            temp_dir.path(),
            "triage-ticket",
            "assess-request",
            "Assess request",
        )?;

        Command::cargo_bin("emc")?
            .args([
                "connect",
                "workflow",
                "--workflow",
                "triage-ticket",
                "--from",
                "assess-request",
                "--to-workflow",
                "close-ticket",
                "--via",
                "outcome",
                "--name",
                "Close accepted request",
                "--reason",
                "Accepted request can be closed.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args(["remove", "workflow", "--slug", "close-ticket"])
            .current_dir(temp_dir.path())
            .assert()
            .failure()
            .stderr(predicate::str::contains(
                "workflow close-ticket is referenced by workflow triage-ticket",
            ));

        Command::cargo_bin("emc")?
            .arg("check")
            .current_dir(temp_dir.path())
            .assert()
            .success();

        assert!(
            exists(temp_dir.path().join("model/lean/CloseTicket.lean"))?,
            "rejected workflow removal must leave Lean artifacts in place"
        );
        assert!(
            exists(temp_dir.path().join("model/quint/CloseTicket.qnt"))?,
            "rejected workflow removal must leave Quint artifacts in place"
        );

        Ok(())
    }

    #[test]
    fn remove_workflow_requires_exact_command_shape() -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        add_workflow(temp_dir.path(), "open-ticket", "Open ticket")?;

        Command::cargo_bin("emc")?
            .args(["remove", "model", "--slug", "open-ticket"])
            .current_dir(temp_dir.path())
            .assert()
            .failure()
            .stderr(predicate::str::contains(
                "usage: emc <command> [arguments]; run emc --help",
            ));

        assert!(
            exists(temp_dir.path().join("model/lean/OpenTicket.lean"))?,
            "malformed workflow removal command must not delete Lean artifacts"
        );
        assert!(
            exists(temp_dir.path().join("model/quint/OpenTicket.qnt"))?,
            "malformed workflow removal command must not delete Quint artifacts"
        );

        Ok(())
    }

    #[test]
    fn remove_workflow_allows_unrelated_workflow_exit_references() -> Result<(), Box<dyn Error>> {
        let temp_dir = TempDir::new()?;

        Command::cargo_bin("emc")?
            .args(["init", "--name", "Repair Desk"])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        add_workflow(temp_dir.path(), "triage-ticket", "Triage ticket")?;
        add_workflow(temp_dir.path(), "close-ticket", "Close ticket")?;
        add_workflow(temp_dir.path(), "archive-ticket", "Archive ticket")?;
        add_slice_to_workflow(
            temp_dir.path(),
            "triage-ticket",
            "assess-request",
            "Assess request",
        )?;

        Command::cargo_bin("emc")?
            .args([
                "connect",
                "workflow",
                "--workflow",
                "triage-ticket",
                "--from",
                "assess-request",
                "--to-workflow",
                "close-ticket",
                "--via",
                "outcome",
                "--name",
                "Close accepted request",
                "--reason",
                "Accepted request can be closed.",
            ])
            .current_dir(temp_dir.path())
            .assert()
            .success();

        Command::cargo_bin("emc")?
            .args(["remove", "workflow", "--slug", "archive-ticket"])
            .current_dir(temp_dir.path())
            .assert()
            .success()
            .stdout(predicate::str::contains("removed workflow Archive ticket"));

        Command::cargo_bin("emc")?
            .arg("check")
            .current_dir(temp_dir.path())
            .assert()
            .success();

        assert!(
            exists(temp_dir.path().join("model/lean/CloseTicket.lean"))?,
            "unrelated workflow exit target Lean module must remain modeled"
        );
        assert!(
            exists(temp_dir.path().join("model/quint/CloseTicket.qnt"))?,
            "unrelated workflow exit target Quint module must remain modeled"
        );

        Ok(())
    }

    fn add_workflow(path: &Path, slug: &str, name: &str) -> Result<(), Box<dyn Error>> {
        Command::cargo_bin("emc")?
            .args([
                "add",
                "workflow",
                "--slug",
                slug,
                "--name",
                name,
                "--description",
                "Actor completes the workflow.",
            ])
            .current_dir(path)
            .assert()
            .success();
        Ok(())
    }

    fn add_slice_to_workflow(
        path: &Path,
        workflow: &str,
        slug: &str,
        name: &str,
    ) -> Result<(), Box<dyn Error>> {
        Command::cargo_bin("emc")?
            .args([
                "add",
                "slice",
                "--workflow",
                workflow,
                "--slug",
                slug,
                "--name",
                name,
                "--type",
                "state_change",
                "--description",
                "Actor captures the request.",
            ])
            .current_dir(path)
            .assert()
            .success();
        Ok(())
    }
}
