#![allow(clippy::missing_errors_doc)]
#![allow(clippy::missing_panics_doc)]

mod common;

use stmo_cli::api::RedashClient;
use wiremock::{MockServer, Mock, ResponseTemplate};
use wiremock::matchers::{method, path, query_param};
use common::*;

#[tokio::test]
async fn test_refresh_query_success() {
    let mock_server = MockServer::start().await;

    mock_refresh_query(123, "test-job-id")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let job = client.refresh_query(123, None).await.unwrap();

    assert_eq!(job.id, "test-job-id");
    assert_eq!(job.status, 1);
    assert!(job.query_result_id.is_none());
}

#[tokio::test]
async fn test_refresh_query_with_parameters() {
    let mock_server = MockServer::start().await;

    Mock::given(method("POST"))
        .and(path("/api/queries/123/results"))
        .respond_with(ResponseTemplate::new(200).set_body_json(
            serde_json::json!({
                "job": {
                    "id": "test-job-id",
                    "status": 1,
                    "query_result_id": null,
                    "error": null
                }
            })
        ))
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let mut params = std::collections::HashMap::new();
    params.insert("start_date".to_string(), serde_json::json!("2025-01-01"));
    params.insert("channels".to_string(), serde_json::json!(["release", "beta"]));

    let job = client.refresh_query(123, Some(params)).await.unwrap();

    assert_eq!(job.id, "test-job-id");
    assert_eq!(job.status, 1);
}

#[tokio::test]
async fn test_poll_job_pending() {
    let mock_server = MockServer::start().await;

    mock_poll_job_pending("test-job-id")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let job = client.poll_job("test-job-id").await.unwrap();

    assert_eq!(job.id, "test-job-id");
    assert_eq!(job.status, 1);
    assert!(job.query_result_id.is_none());
}

#[tokio::test]
async fn test_poll_job_success() {
    let mock_server = MockServer::start().await;

    mock_poll_job_success("test-job-id", 456)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let job = client.poll_job("test-job-id").await.unwrap();

    assert_eq!(job.id, "test-job-id");
    assert_eq!(job.status, 3);
    assert_eq!(job.query_result_id, Some(456));
}

#[tokio::test]
async fn test_poll_job_failure() {
    let mock_server = MockServer::start().await;

    mock_poll_job_failure("test-job-id", "Query execution failed")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let job = client.poll_job("test-job-id").await.unwrap();

    assert_eq!(job.id, "test-job-id");
    assert_eq!(job.status, 4);
    assert_eq!(job.error, Some("Query execution failed".to_string()));
}

#[tokio::test]
async fn test_get_query_result_success() {
    let mock_server = MockServer::start().await;

    mock_get_query_result(123, 456)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.get_query_result(123, 456).await.unwrap();

    assert_eq!(result.id, 456);
    assert_eq!(result.data.columns.len(), 2);
    assert_eq!(result.data.columns[0].name, "col1");
    assert_eq!(result.data.rows.len(), 2);
    assert!((result.runtime - 1.5).abs() < f64::EPSILON);
}

#[tokio::test]
async fn test_execute_query_with_polling_success() {
    let mock_server = MockServer::start().await;

    mock_refresh_query(123, "test-job-id")
        .expect(1)
        .mount(&mock_server)
        .await;

    mock_poll_job_success("test-job-id", 456)
        .expect(1)
        .mount(&mock_server)
        .await;

    mock_get_query_result(123, 456)
        .expect(1)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.execute_query_with_polling(123, None, 10, 100).await.unwrap();

    assert_eq!(result.id, 456);
    assert_eq!(result.data.columns.len(), 2);
    assert_eq!(result.data.rows.len(), 2);
}

#[tokio::test]
async fn test_execute_query_with_polling_failure() {
    let mock_server = MockServer::start().await;

    mock_refresh_query(123, "test-job-id")
        .mount(&mock_server)
        .await;

    mock_poll_job_failure("test-job-id", "Syntax error in SQL")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.execute_query_with_polling(123, None, 10, 100).await;

    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.to_string().contains("Query execution failed"));
    assert!(err.to_string().contains("Syntax error in SQL"));
}

#[tokio::test]
async fn test_execute_query_with_polling_timeout() {
    let mock_server = MockServer::start().await;

    mock_refresh_query(123, "test-job-id")
        .mount(&mock_server)
        .await;

    mock_poll_job_pending("test-job-id")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.execute_query_with_polling(123, None, 1, 100).await;

    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.to_string().contains("timed out"));
}

#[tokio::test]
async fn test_list_my_queries_pagination() {
    let mock_server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/queries/my"))
        .and(query_param("page", "1"))
        .and(query_param("page_size", "100"))
        .respond_with(ResponseTemplate::new(200).set_body_json(
            serde_json::json!({
                "results": [],
                "count": 0,
                "page": 1,
                "page_size": 100
            })
        ))
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let response = client.list_my_queries(1, 100).await.unwrap();

    assert_eq!(response.count, 0);
    assert_eq!(response.page, 1);
    assert_eq!(response.page_size, 100);
}

#[tokio::test]
async fn test_list_data_sources_success() {
    let mock_server = MockServer::start().await;

    mock_list_data_sources()
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let data_sources = client.list_data_sources().await.unwrap();

    assert_eq!(data_sources.len(), 2);
    assert_eq!(data_sources[0].id, 63);
    assert_eq!(data_sources[0].name, "Telemetry (BigQuery)");
    assert_eq!(data_sources[0].ds_type, "bigquery");
    assert_eq!(data_sources[1].id, 10);
    assert_eq!(data_sources[1].name, "Redash metadata");
    assert_eq!(data_sources[1].ds_type, "pg");
}

#[tokio::test]
async fn test_list_data_sources_empty() {
    let mock_server = MockServer::start().await;

    mock_list_data_sources_empty()
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let data_sources = client.list_data_sources().await.unwrap();

    assert_eq!(data_sources.len(), 0);
}

#[tokio::test]
async fn test_get_data_source_success() {
    let mock_server = MockServer::start().await;

    mock_get_data_source(63)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let data_source = client.get_data_source(63).await.unwrap();

    assert_eq!(data_source.id, 63);
    assert_eq!(data_source.name, "Test Data Source");
    assert_eq!(data_source.ds_type, "bigquery");
    assert_eq!(data_source.description, Some("Test description".to_string()));
}

#[tokio::test]
async fn test_get_data_source_not_found() {
    let mock_server = MockServer::start().await;

    mock_get_data_source_not_found(999)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.get_data_source(999).await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_get_data_source_schema_success() {
    let mock_server = MockServer::start().await;

    mock_get_data_source_schema(63)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let schema = client.get_data_source_schema(63, false).await.unwrap();

    assert_eq!(schema.schema.len(), 2);
    assert_eq!(schema.schema[0].name, "table1");
    assert_eq!(schema.schema[0].columns.len(), 3);
    assert_eq!(schema.schema[0].columns[0].name, "col1");
    assert_eq!(schema.schema[0].columns[0].column_type, "STRING");
    assert_eq!(schema.schema[1].name, "table2");
    assert_eq!(schema.schema[1].columns.len(), 2);
    assert_eq!(schema.schema[1].columns[0].name, "id");
    assert_eq!(schema.schema[1].columns[0].column_type, "INTEGER");
}

#[tokio::test]
async fn test_get_data_source_schema_unauthorized() {
    let mock_server = MockServer::start().await;

    mock_get_data_source_schema_unauthorized(63)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.get_data_source_schema(63, false).await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_archive_query_success() {
    let mock_server = MockServer::start().await;

    mock_archive_query(123, "Test Query")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let query = client.archive_query(123).await.unwrap();

    assert_eq!(query.id, 123);
    assert_eq!(query.name, "Test Query");
    assert!(query.is_archived);
}

#[tokio::test]
async fn test_archive_query_not_found() {
    let mock_server = MockServer::start().await;

    mock_archive_query_not_found(999)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.archive_query(999).await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_unarchive_query_success() {
    let mock_server = MockServer::start().await;

    mock_unarchive_query(123, "Test Query")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let query = client.unarchive_query(123).await.unwrap();

    assert_eq!(query.id, 123);
    assert_eq!(query.name, "Test Query");
    assert!(!query.is_archived);
}

#[tokio::test]
async fn test_unarchive_query_forbidden() {
    let mock_server = MockServer::start().await;

    mock_unarchive_query_forbidden(123)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.unarchive_query(123).await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_get_query_archived() {
    let mock_server = MockServer::start().await;

    mock_get_query(123, "Archived Query", true)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let query = client.get_query(123).await.unwrap();

    assert_eq!(query.id, 123);
    assert_eq!(query.name, "Archived Query");
    assert!(query.is_archived);
}

#[tokio::test]
async fn test_get_query_not_archived() {
    let mock_server = MockServer::start().await;

    mock_get_query(123, "Active Query", false)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let query = client.get_query(123).await.unwrap();

    assert_eq!(query.id, 123);
    assert_eq!(query.name, "Active Query");
    assert!(!query.is_archived);
}

#[tokio::test]
async fn test_list_favorite_dashboards_success() {
    let mock_server = MockServer::start().await;

    mock_list_favorite_dashboards(2)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let response = client.list_favorite_dashboards(1, 100).await.unwrap();

    assert_eq!(response.count, 2);
    assert_eq!(response.results.len(), 2);
    assert_eq!(response.results[0].id, 2570);
    assert_eq!(response.results[0].name, "Firefox Desktop on SteamOS");
    assert!(!response.results[0].is_archived);
}

#[tokio::test]
async fn test_list_favorite_dashboards_empty() {
    let mock_server = MockServer::start().await;

    mock_list_favorite_dashboards_empty()
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let response = client.list_favorite_dashboards(1, 100).await.unwrap();

    assert_eq!(response.count, 0);
    assert_eq!(response.results.len(), 0);
}

#[tokio::test]
async fn test_get_dashboard_success() {
    let mock_server = MockServer::start().await;

    mock_get_dashboard(2570, "Test Dashboard", false)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let dashboard = client.get_dashboard("test-dashboard").await.unwrap();

    assert_eq!(dashboard.id, 2570);
    assert_eq!(dashboard.name, "Test Dashboard");
    assert_eq!(dashboard.user_id, 530);
    assert!(!dashboard.is_archived);
    assert!(!dashboard.is_draft);
}

#[tokio::test]
async fn test_get_dashboard_not_found() {
    let mock_server = MockServer::start().await;

    mock_get_dashboard_not_found("nonexistent-dashboard")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.get_dashboard("nonexistent-dashboard").await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_get_dashboard_archived() {
    let mock_server = MockServer::start().await;

    mock_get_dashboard(2570, "Archived Dashboard", true)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let dashboard = client.get_dashboard("archived-dashboard").await.unwrap();

    assert_eq!(dashboard.id, 2570);
    assert_eq!(dashboard.name, "Archived Dashboard");
    assert!(dashboard.is_archived);
}

#[tokio::test]
async fn test_update_dashboard_success() {
    let mock_server = MockServer::start().await;

    mock_update_dashboard(2570, "Updated Dashboard")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();

    let dashboard = stmo_cli::models::Dashboard {
        id: 2570,
        name: "Updated Dashboard".to_string(),
        slug: "updated-dashboard".to_string(),
        user_id: 530,
        is_archived: false,
        is_draft: false,
        filters_enabled: false,
        tags: vec![],
        widgets: vec![],
    };

    let result = client.update_dashboard(&dashboard).await.unwrap();

    assert_eq!(result.id, 2570);
    assert_eq!(result.name, "Updated Dashboard");
}

#[tokio::test]
async fn test_archive_dashboard_success() {
    let mock_server = MockServer::start().await;

    mock_archive_dashboard(2570)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.archive_dashboard(2570).await;

    assert!(result.is_ok());
}

#[tokio::test]
async fn test_archive_dashboard_not_found() {
    let mock_server = MockServer::start().await;

    mock_archive_dashboard_not_found(9999)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.archive_dashboard(9999).await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_unarchive_dashboard_success() {
    let mock_server = MockServer::start().await;

    mock_unarchive_dashboard(2570, "Test Dashboard")
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let dashboard = client.unarchive_dashboard(2570).await.unwrap();

    assert_eq!(dashboard.id, 2570);
    assert_eq!(dashboard.name, "Test Dashboard");
    assert!(!dashboard.is_archived);
}

#[tokio::test]
async fn test_unarchive_dashboard_forbidden() {
    let mock_server = MockServer::start().await;

    mock_unarchive_dashboard_forbidden(2570)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.unarchive_dashboard(2570).await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_create_widget_success() {
    let mock_server = MockServer::start().await;

    mock_create_widget(2570, 75035)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();

    let widget = stmo_cli::models::CreateWidget {
        dashboard_id: 2570,
        visualization_id: None,
        text: "Test Widget".to_string(),
        width: 1,
        options: stmo_cli::models::WidgetOptions {
            position: stmo_cli::models::WidgetPosition {
                col: 0,
                row: 0,
                size_x: 3,
                size_y: 2,
            },
            parameter_mappings: None,
        },
    };

    let result = client.create_widget(&widget).await.unwrap();

    assert_eq!(result.id, 75035);
    assert_eq!(result.dashboard_id, 2570);
}

#[tokio::test]
async fn test_delete_widget_success() {
    let mock_server = MockServer::start().await;

    mock_delete_widget(75035)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.delete_widget(75035).await;

    assert!(result.is_ok());
}

#[tokio::test]
async fn test_delete_widget_not_found() {
    let mock_server = MockServer::start().await;

    mock_delete_widget_not_found(999)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.delete_widget(999).await;

    assert!(result.is_err());
}

#[tokio::test]
async fn test_refresh_query_bad_request_includes_error_body() {
    let mock_server = MockServer::start().await;

    mock_refresh_query_bad_request(
        123,
        "The following parameter values are incompatible with their definitions: worker_pool"
    )
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.refresh_query(123, None).await;

    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.to_string().contains("400"));
    assert!(err.to_string().contains("parameter values are incompatible"));
}

#[tokio::test]
async fn test_refresh_query_forbidden_includes_error_body() {
    let mock_server = MockServer::start().await;

    mock_refresh_query_forbidden(123)
        .mount(&mock_server)
        .await;

    let client = RedashClient::new(mock_server.uri(), "test-key").unwrap();
    let result = client.refresh_query(123, None).await;

    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.to_string().contains("403"));
    assert!(err.to_string().contains("Access denied"));
}
