use serde_json::json;
use std::path::PathBuf;
use tempfile::TempDir;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};

/// Test fixture for CLI integration tests
pub struct TestFixture {
    pub mock_server: MockServer,
    pub config_dir: TempDir,
    pub config_path: PathBuf,
}

impl TestFixture {
    /// Create a new test fixture with a mock API server
    pub async fn new() -> Self {
        let mock_server = MockServer::start().await;
        let config_dir = TempDir::new().expect("Failed to create temp dir");

        // Create attune subdirectory to match actual config path structure
        let attune_dir = config_dir.path().join("attune");
        std::fs::create_dir_all(&attune_dir).expect("Failed to create attune config dir");
        let config_path = attune_dir.join("config.yaml");

        Self {
            mock_server,
            config_dir,
            config_path,
        }
    }

    /// Get the mock server URI
    pub fn server_url(&self) -> String {
        self.mock_server.uri()
    }

    /// Get the config directory path
    pub fn config_dir_path(&self) -> &std::path::Path {
        self.config_dir.path()
    }

    /// Write a test config file with the mock server URL
    pub fn write_config(&self, content: &str) {
        std::fs::write(&self.config_path, content).expect("Failed to write config");
    }

    /// Write a default config with the mock server
    pub fn write_default_config(&self) {
        let config = format!(
            r#"
current_profile: default
default_output_format: table
profiles:
  default:
    api_url: {}
    description: Test server
"#,
            self.server_url()
        );
        self.write_config(&config);
    }

    /// Write a config with authentication tokens
    pub fn write_authenticated_config(&self, access_token: &str, refresh_token: &str) {
        let config = format!(
            r#"
current_profile: default
default_output_format: table
profiles:
  default:
    api_url: {}
    auth_token: {}
    refresh_token: {}
    description: Test server
"#,
            self.server_url(),
            access_token,
            refresh_token
        );
        self.write_config(&config);
    }

    /// Write a config with multiple profiles
    #[allow(dead_code)]
    pub fn write_multi_profile_config(&self) {
        let config = format!(
            r#"
current_profile: default
default_output_format: table
profiles:
  default:
    api_url: {}
    description: Default test server
  staging:
    api_url: https://staging.example.com
    description: Staging environment
  production:
    api_url: https://api.example.com
    description: Production environment
    output_format: json
"#,
            self.server_url()
        );
        self.write_config(&config);
    }
}

/// Mock a successful login response
#[allow(dead_code)]
pub async fn mock_login_success(server: &MockServer, access_token: &str, refresh_token: &str) {
    Mock::given(method("POST"))
        .and(path("/auth/login"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": {
                "access_token": access_token,
                "refresh_token": refresh_token,
                "expires_in": 3600
            }
        })))
        .mount(server)
        .await;
}

/// Mock a failed login response
#[allow(dead_code)]
pub async fn mock_login_failure(server: &MockServer) {
    Mock::given(method("POST"))
        .and(path("/auth/login"))
        .respond_with(ResponseTemplate::new(401).set_body_json(json!({
            "error": "Invalid credentials"
        })))
        .mount(server)
        .await;
}

/// Mock a whoami response
#[allow(dead_code)]
pub async fn mock_whoami_success(server: &MockServer, username: &str, display_name: &str) {
    Mock::given(method("GET"))
        .and(path("/auth/me"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": {
                "id": 1,
                "login": username,
                "display_name": display_name
            }
        })))
        .mount(server)
        .await;
}

/// Mock an unauthorized response
#[allow(dead_code)]
pub async fn mock_unauthorized(server: &MockServer, path_pattern: &str) {
    Mock::given(method("GET"))
        .and(path(path_pattern))
        .respond_with(ResponseTemplate::new(401).set_body_json(json!({
            "error": "Unauthorized"
        })))
        .mount(server)
        .await;
}

/// Mock a pack list response
#[allow(dead_code)]
pub async fn mock_pack_list(server: &MockServer) {
    Mock::given(method("GET"))
        .and(path("/api/v1/packs"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "ref": "core",
                    "label": "Core Pack",
                    "description": "Core pack",
                    "version": "1.0.0",
                    "author": "Attune",
                    "enabled": true,
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                },
                {
                    "id": 2,
                    "ref": "linux",
                    "label": "Linux Pack",
                    "description": "Linux automation pack",
                    "version": "1.0.0",
                    "author": "Attune",
                    "enabled": true,
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                }
            ]
        })))
        .mount(server)
        .await;
}

/// Mock a pack get response
#[allow(dead_code)]
pub async fn mock_pack_get(server: &MockServer, pack_ref: &str) {
    let path_pattern = format!("/api/v1/packs/{}", pack_ref);
    // Capitalize first letter for label
    let label = pack_ref
        .chars()
        .enumerate()
        .map(|(i, c)| {
            if i == 0 {
                c.to_uppercase().next().unwrap()
            } else {
                c
            }
        })
        .collect::<String>();
    Mock::given(method("GET"))
        .and(path(path_pattern.as_str()))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": {
                "id": 1,
                "ref": pack_ref,
                "label": format!("{} Pack", label),
                "description": format!("{} pack", pack_ref),
                "version": "1.0.0",
                "author": "Attune",
                "enabled": true,
                "created": "2024-01-01T00:00:00Z",
                "updated": "2024-01-01T00:00:00Z"
            }
        })))
        .mount(server)
        .await;
}

/// Mock an action list response
#[allow(dead_code)]
pub async fn mock_action_list(server: &MockServer) {
    Mock::given(method("GET"))
        .and(path("/api/v1/actions"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "ref": "core.echo",
                    "pack_ref": "core",
                    "label": "Echo Action",
                    "description": "Echo a message",
                    "entrypoint": "echo.py",
                    "runtime": null,
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                }
            ],
            "meta": {
                "page": 1,
                "limit": 50,
                "total": 1,
                "total_pages": 1
            }
        })))
        .mount(server)
        .await;
}

/// Mock an action execution response
#[allow(dead_code)]
pub async fn mock_action_execute(server: &MockServer, execution_id: i64) {
    Mock::given(method("POST"))
        .and(path("/api/v1/executions/execute"))
        .respond_with(ResponseTemplate::new(201).set_body_json(json!({
            "data": {
                "id": execution_id,
                "action": 1,
                "action_ref": "core.echo",
                "config": {},
                "parent": null,
                "enforcement": null,
                "executor": null,
                "status": "scheduled",
                "result": null,
                "created": "2024-01-01T00:00:00Z",
                "updated": "2024-01-01T00:00:00Z"
            }
        })))
        .mount(server)
        .await;
}

/// Mock an execution get response
#[allow(dead_code)]
pub async fn mock_execution_get(server: &MockServer, execution_id: i64, status: &str) {
    let path_pattern = format!("/api/v1/executions/{}", execution_id);
    Mock::given(method("GET"))
        .and(path(path_pattern.as_str()))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": {
                "id": execution_id,
                "action": 1,
                "action_ref": "core.echo",
                "config": {"message": "Hello"},
                "parent": null,
                "enforcement": null,
                "executor": null,
                "status": status,
                "result": {"output": "Hello"},
                "created": "2024-01-01T00:00:00Z",
                "updated": "2024-01-01T00:00:00Z"
            }
        })))
        .mount(server)
        .await;
}

/// Mock an execution list response with filters
#[allow(dead_code)]
pub async fn mock_execution_list(server: &MockServer) {
    Mock::given(method("GET"))
        .and(path("/api/v1/executions"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "action_ref": "core.echo",
                    "status": "succeeded",
                    "parent": null,
                    "enforcement": null,
                    "result": {"output": "Hello"},
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                },
                {
                    "id": 2,
                    "action_ref": "core.echo",
                    "status": "failed",
                    "parent": null,
                    "enforcement": null,
                    "result": {"error": "Command failed"},
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                }
            ]
        })))
        .mount(server)
        .await;
}

/// Mock a rule list response
#[allow(dead_code)]
pub async fn mock_rule_list(server: &MockServer) {
    Mock::given(method("GET"))
        .and(path("/api/v1/rules"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "ref": "core.on_webhook",
                    "pack": 1,
                    "pack_ref": "core",
                    "label": "On Webhook",
                    "description": "Handle webhook events",
                    "trigger": 1,
                    "trigger_ref": "core.webhook",
                    "action": 1,
                    "action_ref": "core.echo",
                    "enabled": true,
                    "conditions": {},
                    "action_params": {},
                    "trigger_params": {},
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                }
            ]
        })))
        .mount(server)
        .await;
}

/// Mock a trigger list response
#[allow(dead_code)]
pub async fn mock_trigger_list(server: &MockServer) {
    Mock::given(method("GET"))
        .and(path("/api/v1/triggers"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "ref": "core.webhook",
                    "pack": 1,
                    "pack_ref": "core",
                    "label": "Webhook Trigger",
                    "description": "Webhook trigger",
                    "enabled": true,
                    "param_schema": {},
                    "out_schema": {},
                    "webhook_enabled": false,
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                }
            ]
        })))
        .mount(server)
        .await;
}

/// Mock a sensor list response
#[allow(dead_code)]
pub async fn mock_sensor_list(server: &MockServer) {
    Mock::given(method("GET"))
        .and(path("/api/v1/sensors"))
        .respond_with(ResponseTemplate::new(200).set_body_json(json!({
            "data": [
                {
                    "id": 1,
                    "ref": "core.webhook_sensor",
                    "pack": 1,
                    "pack_ref": "core",
                    "label": "Webhook Sensor",
                    "description": "Webhook sensor",
                    "enabled": true,
                    "trigger_types": ["core.webhook"],
                    "entry_point": "webhook_sensor.py",
                    "created": "2024-01-01T00:00:00Z",
                    "updated": "2024-01-01T00:00:00Z"
                }
            ]
        })))
        .mount(server)
        .await;
}

/// Mock a 404 not found response
#[allow(dead_code)]
pub async fn mock_not_found(server: &MockServer, path_pattern: &str) {
    Mock::given(method("GET"))
        .and(path(path_pattern))
        .respond_with(ResponseTemplate::new(404).set_body_json(json!({
            "error": "Not found"
        })))
        .mount(server)
        .await;
}

/// Mock a successful pack create response (POST /api/v1/packs)
#[allow(dead_code)]
pub async fn mock_pack_create(server: &MockServer) {
    Mock::given(method("POST"))
        .and(path("/api/v1/packs"))
        .respond_with(ResponseTemplate::new(201).set_body_json(json!({
            "data": {
                "id": 42,
                "ref": "my_pack",
                "label": "My Pack",
                "description": "A test pack",
                "version": "0.1.0",
                "author": null,
                "enabled": true,
                "tags": ["test"],
                "created": "2024-01-01T00:00:00Z",
                "updated": "2024-01-01T00:00:00Z"
            }
        })))
        .mount(server)
        .await;
}

/// Mock a 409 conflict response for pack create
#[allow(dead_code)]
pub async fn mock_pack_create_conflict(server: &MockServer) {
    Mock::given(method("POST"))
        .and(path("/api/v1/packs"))
        .respond_with(ResponseTemplate::new(409).set_body_json(json!({
            "error": "Pack with ref 'my_pack' already exists"
        })))
        .mount(server)
        .await;
}
