//! Integration tests for CLI workflow commands

#![allow(deprecated)]

use assert_cmd::Command;
use predicates::prelude::*;
use serde_json::json;
use std::fs;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

mod common;
use common::*;

// ── Mock helpers ────────────────────────────────────────────────────────

async fn mock_workflow_list(server: &MockServer) {
    Mock::given(method("GET"))
        .and(path("/api/v1/workflows"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "ref": "core.install_packs",
                    "pack_ref": "core",
                    "label": "Install Packs",
                    "description": "Install one or more packs",
                    "version": "1.0.0",
                    "tags": ["core", "packs"],
                    "enabled": true,
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                },
                {
                    "id": 2,
                    "ref": "mypack.deploy",
                    "pack_ref": "mypack",
                    "label": "Deploy App",
                    "description": "Deploy an application",
                    "version": "2.0.0",
                    "tags": ["deploy"],
                    "enabled": true,
                    "created": "2024-01-02T00:00:00Z",
                    "updated": "2024-01-02T00:00:00Z"
                }
            ]
        })))
        .mount(server)
        .await;
}

async fn mock_workflow_list_by_pack(server: &MockServer, pack_ref: &str) {
    let p = format!("/api/v1/packs/{}/workflows", pack_ref);
    Mock::given(method("GET"))
        .and(path(p.as_str()))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "ref": format!("{}.example_workflow", pack_ref),
                    "pack_ref": pack_ref,
                    "label": "Example Workflow",
                    "description": "An example workflow",
                    "version": "1.0.0",
                    "tags": [],
                    "enabled": true,
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                }
            ]
        })))
        .mount(server)
        .await;
}

async fn mock_workflow_get(server: &MockServer, workflow_ref: &str) {
    let p = format!("/api/v1/workflows/{}", workflow_ref);
    Mock::given(method("GET"))
        .and(path(p.as_str()))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": {
                "id": 1,
                "ref": workflow_ref,
                "pack": 1,
                "pack_ref": "mypack",
                "label": "My Workflow",
                "description": "A test workflow",
                "version": "1.0.0",
                "param_schema": {
                    "url": {"type": "string", "required": true},
                    "timeout": {"type": "integer", "default": 30}
                },
                "out_schema": {
                    "status": {"type": "string"}
                },
                "definition": {
                    "version": "1.0.0",
                    "vars": {"result": null},
                    "tasks": [
                        {
                            "name": "step1",
                            "action": "core.echo",
                            "input": {"message": "hello"},
                            "next": [
                                {"when": "{{ succeeded() }}", "do": ["step2"]}
                            ]
                        },
                        {
                            "name": "step2",
                            "action": "core.echo",
                            "input": {"message": "done"}
                        }
                    ]
                },
                "tags": ["test", "demo"],
                "enabled": true,
                "created": "2024-01-01T00:00:00Z",
                "updated": "2024-01-01T00:00:00Z"
            }
        })))
        .mount(server)
        .await;
}

async fn mock_workflow_delete(server: &MockServer, workflow_ref: &str) {
    let p = format!("/api/v1/workflows/{}", workflow_ref);
    Mock::given(method("DELETE"))
        .and(path(p.as_str()))
        .respond_with(ResponseTemplate::new(204))
        .mount(server)
        .await;
}

async fn mock_workflow_save(server: &MockServer, pack_ref: &str) {
    let p = format!("/api/v1/packs/{}/workflow-files", pack_ref);
    Mock::given(method("POST"))
        .and(path(p.as_str()))
        .respond_with(ResponseTemplate::new(201).set_body_json(json!({
            "data": {
                "id": 10,
                "ref": format!("{}.deploy", pack_ref),
                "pack": 1,
                "pack_ref": pack_ref,
                "label": "Deploy App",
                "description": "Deploy the application",
                "version": "1.0.0",
                "param_schema": null,
                "out_schema": null,
                "definition": {"version": "1.0.0", "tasks": []},
                "tags": ["deploy"],
                "enabled": true,
                "created": "2024-01-10T00:00:00Z",
                "updated": "2024-01-10T00:00:00Z"
            }
        })))
        .mount(server)
        .await;
}

async fn mock_workflow_save_conflict(server: &MockServer, pack_ref: &str) {
    let p = format!("/api/v1/packs/{}/workflow-files", pack_ref);
    Mock::given(method("POST"))
        .and(path(p.as_str()))
        .respond_with(ResponseTemplate::new(409).set_body_json(json!({
            "error": "Workflow with ref 'mypack.deploy' already exists"
        })))
        .mount(server)
        .await;
}

async fn mock_workflow_update(server: &MockServer, workflow_ref: &str) {
    let p = format!("/api/v1/workflows/{}/file", workflow_ref);
    Mock::given(method("PUT"))
        .and(path(p.as_str()))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": {
                "id": 10,
                "ref": workflow_ref,
                "pack": 1,
                "pack_ref": "mypack",
                "label": "Deploy App",
                "description": "Deploy the application",
                "version": "1.0.0",
                "param_schema": null,
                "out_schema": null,
                "definition": {"version": "1.0.0", "tasks": []},
                "tags": ["deploy"],
                "enabled": true,
                "created": "2024-01-10T00:00:00Z",
                "updated": "2024-01-10T12:00:00Z"
            }
        })))
        .mount(server)
        .await;
}

// ── Helper to write action + workflow YAML to temp dirs ─────────────────

struct WorkflowFixture {
    _dir: tempfile::TempDir,
    action_yaml_path: String,
}

impl WorkflowFixture {
    fn new(action_ref: &str, workflow_file: &str) -> Self {
        let dir = tempfile::TempDir::new().expect("Failed to create temp dir");
        let actions_dir = dir.path().join("actions");
        let workflows_dir = actions_dir.join("workflows");
        fs::create_dir_all(&workflows_dir).unwrap();

        // Write the action YAML
        let action_yaml = format!(
            r#"ref: {}
label: "Deploy App"
description: "Deploy the application"
enabled: true
workflow_file: {}

parameters:
  environment:
    type: string
    required: true
    description: "Target environment"
  version:
    type: string
    default: "latest"

output:
  status:
    type: string

tags:
  - deploy
"#,
            action_ref, workflow_file,
        );

        let action_name = action_ref.rsplit('.').next().unwrap();
        let action_path = actions_dir.join(format!("{}.yaml", action_name));
        fs::write(&action_path, &action_yaml).unwrap();

        // Write the workflow YAML
        let workflow_yaml = r#"version: "1.0.0"

vars:
  deploy_result: null

tasks:
  - name: prepare
    action: core.echo
    input:
      message: "Preparing deployment"
    next:
      - when: "{{ succeeded() }}"
        do:
          - deploy

  - name: deploy
    action: core.echo
    input:
      message: "Deploying"
    next:
      - when: "{{ succeeded() }}"
        do:
          - verify

  - name: verify
    action: core.echo
    input:
      message: "Verifying"

output_map:
  status: "{{ 'success' if workflow.deploy_result else 'unknown' }}"
"#;

        let workflow_path = workflows_dir.join(format!("{}.workflow.yaml", action_name));
        fs::write(&workflow_path, workflow_yaml).unwrap();

        Self {
            action_yaml_path: action_path.to_string_lossy().to_string(),
            _dir: dir,
        }
    }
}

// ── List tests ──────────────────────────────────────────────────────────

#[tokio::test]
async fn test_workflow_list_authenticated() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_list(&fixture.mock_server).await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("list");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("core.install_packs"))
        .stdout(predicate::str::contains("mypack.deploy"))
        .stdout(predicate::str::contains("2 workflow(s) found"));
}

#[tokio::test]
async fn test_workflow_list_by_pack() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_list_by_pack(&fixture.mock_server, "core").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("list")
        .arg("--pack")
        .arg("core");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("core.example_workflow"))
        .stdout(predicate::str::contains("1 workflow(s) found"));
}

#[tokio::test]
async fn test_workflow_list_json_output() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_list(&fixture.mock_server).await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("--json")
        .arg("workflow")
        .arg("list");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("\"core.install_packs\""))
        .stdout(predicate::str::contains("\"mypack.deploy\""));
}

#[tokio::test]
async fn test_workflow_list_yaml_output() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_list(&fixture.mock_server).await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("--yaml")
        .arg("workflow")
        .arg("list");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("core.install_packs"))
        .stdout(predicate::str::contains("mypack.deploy"));
}

#[tokio::test]
async fn test_workflow_list_empty() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    Mock::given(method("GET"))
        .and(path("/api/v1/workflows"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": []
        })))
        .mount(&fixture.mock_server)
        .await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("list");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("No workflows found"));
}

#[tokio::test]
async fn test_workflow_list_unauthenticated() {
    let fixture = TestFixture::new().await;
    fixture.write_default_config();

    mock_unauthorized(&fixture.mock_server, "/api/v1/workflows").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("list");

    cmd.assert().failure();
}

// ── Show tests ──────────────────────────────────────────────────────────

#[tokio::test]
async fn test_workflow_show() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_get(&fixture.mock_server, "mypack.my_workflow").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("show")
        .arg("mypack.my_workflow");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("mypack.my_workflow"))
        .stdout(predicate::str::contains("My Workflow"))
        .stdout(predicate::str::contains("1.0.0"))
        .stdout(predicate::str::contains("test, demo"))
        // Tasks table should show task names
        .stdout(predicate::str::contains("step1"))
        .stdout(predicate::str::contains("step2"))
        .stdout(predicate::str::contains("core.echo"));
}

#[tokio::test]
async fn test_workflow_show_json_output() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_get(&fixture.mock_server, "mypack.my_workflow").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("--json")
        .arg("workflow")
        .arg("show")
        .arg("mypack.my_workflow");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("\"mypack.my_workflow\""))
        .stdout(predicate::str::contains("\"My Workflow\""))
        .stdout(predicate::str::contains("\"definition\""));
}

#[tokio::test]
async fn test_workflow_show_not_found() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_not_found(&fixture.mock_server, "/api/v1/workflows/nonexistent.wf").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("show")
        .arg("nonexistent.wf");

    cmd.assert().failure();
}

// ── Delete tests ────────────────────────────────────────────────────────

#[tokio::test]
async fn test_workflow_delete_with_yes_flag() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_delete(&fixture.mock_server, "mypack.my_workflow").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("delete")
        .arg("mypack.my_workflow")
        .arg("--yes");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("deleted successfully"));
}

#[tokio::test]
async fn test_workflow_delete_json_output() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    mock_workflow_delete(&fixture.mock_server, "mypack.my_workflow").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("--json")
        .arg("workflow")
        .arg("delete")
        .arg("mypack.my_workflow")
        .arg("--yes");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("\"message\""))
        .stdout(predicate::str::contains("deleted"));
}

// ── Upload tests ────────────────────────────────────────────────────────

#[tokio::test]
async fn test_workflow_upload_success() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");

    mock_workflow_save(&fixture.mock_server, "mypack").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("upload")
        .arg(&wf_fixture.action_yaml_path);

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("uploaded successfully"))
        .stdout(predicate::str::contains("mypack.deploy"));
}

#[tokio::test]
async fn test_workflow_upload_json_output() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");

    mock_workflow_save(&fixture.mock_server, "mypack").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("--json")
        .arg("workflow")
        .arg("upload")
        .arg(&wf_fixture.action_yaml_path);

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("\"mypack.deploy\""))
        .stdout(predicate::str::contains("\"Deploy App\""));
}

#[tokio::test]
async fn test_workflow_upload_conflict_without_force() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");

    mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("upload")
        .arg(&wf_fixture.action_yaml_path);

    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("already exists"))
        .stderr(predicate::str::contains("--force"));
}

#[tokio::test]
async fn test_workflow_upload_conflict_with_force() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    let wf_fixture = WorkflowFixture::new("mypack.deploy", "workflows/deploy.workflow.yaml");

    mock_workflow_save_conflict(&fixture.mock_server, "mypack").await;
    mock_workflow_update(&fixture.mock_server, "mypack.deploy").await;

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("upload")
        .arg(&wf_fixture.action_yaml_path)
        .arg("--force");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("uploaded successfully"));
}

#[tokio::test]
async fn test_workflow_upload_missing_action_file() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("upload")
        .arg("/nonexistent/path/action.yaml");

    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("not found"));
}

#[tokio::test]
async fn test_workflow_upload_missing_workflow_file() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    // Create a temp dir with only the action YAML, no workflow file
    let dir = tempfile::TempDir::new().unwrap();
    let actions_dir = dir.path().join("actions");
    fs::create_dir_all(&actions_dir).unwrap();

    let action_yaml = r#"ref: mypack.deploy
label: "Deploy App"
workflow_file: workflows/deploy.workflow.yaml
"#;
    let action_path = actions_dir.join("deploy.yaml");
    fs::write(&action_path, action_yaml).unwrap();

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("upload")
        .arg(action_path.to_string_lossy().as_ref());

    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("Workflow file not found"));
}

#[tokio::test]
async fn test_workflow_upload_action_without_workflow_file_field() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    // Create a temp dir with a regular (non-workflow) action YAML
    let dir = tempfile::TempDir::new().unwrap();
    let actions_dir = dir.path().join("actions");
    fs::create_dir_all(&actions_dir).unwrap();

    let action_yaml = r#"ref: mypack.echo
label: "Echo"
description: "A regular action, not a workflow"
runner_type: shell
entry_point: echo.sh
"#;
    let action_path = actions_dir.join("echo.yaml");
    fs::write(&action_path, action_yaml).unwrap();

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("upload")
        .arg(action_path.to_string_lossy().as_ref());

    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("workflow_file"));
}

#[tokio::test]
async fn test_workflow_upload_invalid_action_yaml() {
    let fixture = TestFixture::new().await;
    fixture.write_authenticated_config("valid_token", "refresh_token");

    let dir = tempfile::TempDir::new().unwrap();
    let bad_yaml_path = dir.path().join("bad.yaml");
    fs::write(&bad_yaml_path, "this is not valid yaml: [[[").unwrap();

    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.env("XDG_CONFIG_HOME", fixture.config_dir_path())
        .env("HOME", fixture.config_dir_path())
        .arg("--api-url")
        .arg(fixture.server_url())
        .arg("workflow")
        .arg("upload")
        .arg(bad_yaml_path.to_string_lossy().as_ref());

    cmd.assert()
        .failure()
        .stderr(predicate::str::contains("Failed to parse action YAML"));
}

// ── Help text tests ─────────────────────────────────────────────────────

#[tokio::test]
async fn test_workflow_help() {
    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.arg("workflow").arg("--help");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("upload"))
        .stdout(predicate::str::contains("list"))
        .stdout(predicate::str::contains("show"))
        .stdout(predicate::str::contains("delete"));
}

#[tokio::test]
async fn test_workflow_upload_help() {
    let mut cmd = Command::cargo_bin("attune").unwrap();
    cmd.arg("workflow").arg("upload").arg("--help");

    cmd.assert()
        .success()
        .stdout(predicate::str::contains("action"))
        .stdout(predicate::str::contains("workflow_file"))
        .stdout(predicate::str::contains("--force"));
}
