use anyhow::Result;
use datadog_api_client::datadogV1::api_monitors::{
    DeleteMonitorOptionalParams, GetMonitorOptionalParams, ListMonitorsOptionalParams, MonitorsAPI,
    SearchMonitorsOptionalParams,
};
use datadog_api_client::datadogV1::model::Monitor;

use crate::config::Config;
use crate::formatter::{self, Metadata};
use crate::util;

pub async fn list(
    cfg: &Config,
    name: Option<String>,
    tags: Option<String>,
    limit: i32,
    page: i64,
) -> Result<()> {
    if !(1..=1000).contains(&limit) {
        anyhow::bail!("--limit must be between 1 and 1000, got {limit}");
    }

    let api = crate::make_api!(MonitorsAPI, cfg);

    let mut params = ListMonitorsOptionalParams::default();
    if let Some(name) = name {
        params = params.name(name);
    }
    if let Some(tags) = tags {
        params = params.monitor_tags(tags);
    }
    params = params.page_size(limit).page(page);

    let monitors = api
        .list_monitors(params)
        .await
        .map_err(|e| anyhow::anyhow!("failed to list monitors: {:?}", e))?;

    if monitors.is_empty() {
        eprintln!("No monitors found matching the specified criteria.");
        return Ok(());
    }

    let meta = Metadata {
        count: Some(monitors.len()),
        truncated: false,
        command: Some("monitors list".to_string()),
        next_action: None,
    };
    formatter::format_and_print(&monitors, &cfg.output_format, cfg.agent_mode, Some(&meta))?;
    Ok(())
}

pub async fn get(cfg: &Config, monitor_id: i64) -> Result<()> {
    let api = crate::make_api!(MonitorsAPI, cfg);
    let resp = api
        .get_monitor(monitor_id, GetMonitorOptionalParams::default())
        .await
        .map_err(|e| anyhow::anyhow!("failed to get monitor: {:?}", e))?;
    let meta = Metadata {
        count: None,
        truncated: false,
        command: Some("monitors get".to_string()),
        next_action: None,
    };
    formatter::format_and_print(&resp, &cfg.output_format, cfg.agent_mode, Some(&meta))
}

pub async fn create(cfg: &Config, file: &str) -> Result<()> {
    let body: Monitor = util::read_json_file(file)?;
    let api = crate::make_api!(MonitorsAPI, cfg);
    let resp = api
        .create_monitor(body)
        .await
        .map_err(|e| anyhow::anyhow!("failed to create monitor: {:?}", e))?;
    formatter::output(cfg, &resp)
}

pub async fn update(cfg: &Config, monitor_id: i64, file: &str) -> Result<()> {
    let body: datadog_api_client::datadogV1::model::MonitorUpdateRequest =
        util::read_json_file(file)?;
    let api = crate::make_api!(MonitorsAPI, cfg);
    let resp = api
        .update_monitor(monitor_id, body)
        .await
        .map_err(|e| anyhow::anyhow!("failed to update monitor: {:?}", e))?;
    formatter::output(cfg, &resp)
}

pub async fn search(
    cfg: &Config,
    query: Option<String>,
    page: i64,
    per_page: i64,
    sort: Option<String>,
) -> Result<()> {
    let api = crate::make_api!(MonitorsAPI, cfg);

    let mut params = SearchMonitorsOptionalParams::default()
        .page(page)
        .per_page(per_page);
    if let Some(q) = query {
        params = params.query(q);
    }
    if let Some(s) = sort {
        params = params.sort(s);
    }

    let resp = api
        .search_monitors(params)
        .await
        .map_err(|e| anyhow::anyhow!("failed to search monitors: {:?}", e))?;
    formatter::output(cfg, &resp)
}

pub async fn delete(cfg: &Config, monitor_id: i64) -> Result<()> {
    let api = crate::make_api!(MonitorsAPI, cfg);
    let resp = api
        .delete_monitor(monitor_id, DeleteMonitorOptionalParams::default())
        .await
        .map_err(|e| anyhow::anyhow!("failed to delete monitor: {:?}", e))?;
    formatter::output(cfg, &resp)
}

#[cfg(test)]
mod tests {

    use crate::test_support::*;

    #[tokio::test]
    async fn test_monitors_list_empty() {
        let _lock = lock_env().await;
        let mut server = mockito::Server::new_async().await;
        let cfg = test_config(&server.url());
        let _mock = mock_any(&mut server, "GET", "[]").await;

        let result = super::list(&cfg, None, None, 10, 0).await;
        assert!(result.is_ok(), "monitors list failed: {:?}", result.err());
        cleanup_env();
    }

    #[tokio::test]
    async fn test_monitors_list_with_results() {
        let _lock = lock_env().await;
        let mut server = mockito::Server::new_async().await;
        let cfg = test_config(&server.url());

        let body = r#"[{"id": 1, "name": "Test Monitor", "type": "metric alert", "query": "avg(last_5m):avg:system.cpu.user{*} > 90", "message": "CPU high", "tags": [], "options": {}}]"#;
        let _mock = mock_any(&mut server, "GET", body).await;

        let result = super::list(&cfg, Some("Test".into()), None, 10, 0).await;
        assert!(
            result.is_ok(),
            "monitors list with results failed: {:?}",
            result.err()
        );
        cleanup_env();
    }

    #[tokio::test]
    async fn test_monitors_get() {
        let _lock = lock_env().await;
        let mut server = mockito::Server::new_async().await;
        let cfg = test_config(&server.url());

        let body = r#"{"id": 12345, "name": "Test Monitor", "type": "metric alert", "query": "avg(last_5m):avg:system.cpu.user{*} > 90", "message": "CPU high", "tags": [], "options": {}}"#;
        let _mock = mock_any(&mut server, "GET", body).await;

        let result = super::get(&cfg, 12345).await;
        assert!(result.is_ok(), "monitors get failed: {:?}", result.err());
        cleanup_env();
    }

    #[tokio::test]
    async fn test_monitors_search() {
        let _lock = lock_env().await;
        let mut server = mockito::Server::new_async().await;
        let cfg = test_config(&server.url());

        let body = r#"{"monitors": [], "metadata": {"page": 0, "page_count": 0, "per_page": 30, "total_count": 0}}"#;
        let _mock = mock_any(&mut server, "GET", body).await;

        let result = super::search(&cfg, Some("cpu".into()), 0, 30, None).await;
        assert!(result.is_ok(), "monitors search failed: {:?}", result.err());
        cleanup_env();
    }

    #[tokio::test]
    async fn test_monitors_list_limit_too_small() {
        let cfg = test_config("http://unused.local");
        let result = super::list(&cfg, None, None, 0, 0).await;
        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("--limit must be between 1 and 1000"));
    }

    #[tokio::test]
    async fn test_monitors_list_limit_too_large() {
        let cfg = test_config("http://unused.local");
        let result = super::list(&cfg, None, None, 1001, 0).await;
        assert!(result.is_err());
        assert!(result
            .unwrap_err()
            .to_string()
            .contains("--limit must be between 1 and 1000"));
    }

    #[tokio::test]
    async fn test_monitors_delete() {
        let _lock = lock_env().await;
        let mut server = mockito::Server::new_async().await;
        let cfg = test_config(&server.url());
        let _mock = mock_any(&mut server, "DELETE", r#"{"deleted_monitor_id": 12345}"#).await;

        let result = super::delete(&cfg, 12345).await;
        assert!(result.is_ok(), "monitors delete failed: {:?}", result.err());
        cleanup_env();
    }
}
