use actix_web::{web, HttpResponse};
use chrono::{DateTime, Utc};

use crate::app_state::AppState;
use crate::handlers::agent::schedules::types::{CreateScheduleRequest, PatchScheduleRequest};
use crate::model_config_helper::get_default_model_from_config;
use crate::schedules::{MisFirePolicy, OverlapPolicy, ScheduleRunConfig, ScheduleTrigger};

pub(super) fn validate_schedule_name(name: &str) -> Result<String, HttpResponse> {
    let trimmed = name.trim();
    if trimmed.is_empty() {
        return Err(HttpResponse::BadRequest().json(serde_json::json!({
            "error": "name is required"
        })));
    }
    Ok(trimmed.to_string())
}

fn validate_interval_seconds(interval_seconds: u64) -> Result<(), HttpResponse> {
    if interval_seconds == 0 {
        return Err(HttpResponse::BadRequest().json(serde_json::json!({
            "error": "trigger.every_seconds must be > 0"
        })));
    }
    Ok(())
}

pub(super) fn validate_schedule_trigger(trigger: &ScheduleTrigger) -> Result<(), HttpResponse> {
    match trigger {
        ScheduleTrigger::Interval { every_seconds, .. } => {
            validate_interval_seconds(*every_seconds)
        }
        ScheduleTrigger::Daily {
            hour,
            minute,
            second,
        } => validate_hms(*hour, *minute, *second),
        ScheduleTrigger::Weekly {
            weekdays,
            hour,
            minute,
            second,
        } => {
            if weekdays.is_empty() {
                return Err(HttpResponse::BadRequest().json(serde_json::json!({
                    "error": "trigger.weekdays must not be empty"
                })));
            }
            validate_hms(*hour, *minute, *second)
        }
        ScheduleTrigger::Monthly {
            days,
            hour,
            minute,
            second,
        } => {
            if days.is_empty() {
                return Err(HttpResponse::BadRequest().json(serde_json::json!({
                    "error": "trigger.days must not be empty"
                })));
            }
            if days.iter().any(|day| *day == 0 || *day > 31) {
                return Err(HttpResponse::BadRequest().json(serde_json::json!({
                    "error": "trigger.days values must be between 1 and 31"
                })));
            }
            validate_hms(*hour, *minute, *second)
        }
        ScheduleTrigger::Cron { expr } => {
            if expr.trim().is_empty() {
                return Err(HttpResponse::BadRequest().json(serde_json::json!({
                    "error": "trigger.expr is required"
                })));
            }
            Ok(())
        }
    }
}

pub(super) fn validate_schedule_window(
    start_at: Option<DateTime<Utc>>,
    end_at: Option<DateTime<Utc>>,
) -> Result<(), HttpResponse> {
    if let (Some(start_at), Some(end_at)) = (start_at, end_at) {
        if start_at >= end_at {
            return Err(HttpResponse::BadRequest().json(serde_json::json!({
                "error": "start_at must be earlier than end_at"
            })));
        }
    }
    Ok(())
}

pub(super) fn validate_trigger_api_fields(
    trigger: Option<&ScheduleTrigger>,
    timezone: Option<&str>,
    start_at: Option<DateTime<Utc>>,
    end_at: Option<DateTime<Utc>>,
    misfire_policy: Option<MisFirePolicy>,
    overlap_policy: Option<OverlapPolicy>,
) -> Result<(), HttpResponse> {
    if let Some(trigger) = trigger {
        validate_schedule_trigger(trigger)?;
    }

    if let Some(timezone) = timezone.map(str::trim) {
        if timezone.is_empty() {
            return Err(HttpResponse::BadRequest().json(serde_json::json!({
                "error": "timezone must not be empty when provided"
            })));
        }
    }

    validate_schedule_window(start_at, end_at)?;

    let _ = misfire_policy;
    let _ = overlap_policy;

    Ok(())
}

#[derive(Debug)]
pub(super) struct ResolvedCreateScheduleDefinition {
    pub definition: crate::schedules::store::ScheduleDefinitionChanges,
}

#[derive(Debug)]
pub(super) struct ResolvedPatchScheduleDefinition {
    pub definition: crate::schedules::store::ScheduleDefinitionChanges,
}

pub(super) fn resolve_create_schedule_definition(
    req: &CreateScheduleRequest,
) -> Result<ResolvedCreateScheduleDefinition, HttpResponse> {
    validate_trigger_api_fields(
        Some(&req.trigger),
        req.timezone.as_deref(),
        req.start_at,
        req.end_at,
        req.misfire_policy,
        req.overlap_policy,
    )?;

    Ok(ResolvedCreateScheduleDefinition {
        definition: crate::schedules::store::ScheduleDefinitionChanges {
            trigger: Some(req.trigger.clone()),
            timezone: req.timezone.clone(),
            start_at: req.start_at,
            end_at: req.end_at,
            misfire_policy: req.misfire_policy,
            overlap_policy: req.overlap_policy,
        },
    })
}

pub(super) fn resolve_patch_schedule_definition(
    req: &PatchScheduleRequest,
) -> Result<ResolvedPatchScheduleDefinition, HttpResponse> {
    validate_trigger_api_fields(
        req.trigger.as_ref(),
        req.timezone.as_deref(),
        req.start_at,
        req.end_at,
        req.misfire_policy,
        req.overlap_policy,
    )?;

    Ok(ResolvedPatchScheduleDefinition {
        definition: crate::schedules::store::ScheduleDefinitionChanges {
            trigger: req.trigger.clone(),
            timezone: req.timezone.clone(),
            start_at: req.start_at,
            end_at: req.end_at,
            misfire_policy: req.misfire_policy,
            overlap_policy: req.overlap_policy,
        },
    })
}

fn validate_hms(hour: u8, minute: u8, second: u8) -> Result<(), HttpResponse> {
    if hour > 23 {
        return Err(HttpResponse::BadRequest().json(serde_json::json!({
            "error": "trigger.hour must be between 0 and 23"
        })));
    }
    if minute > 59 {
        return Err(HttpResponse::BadRequest().json(serde_json::json!({
            "error": "trigger.minute must be between 0 and 59"
        })));
    }
    if second > 59 {
        return Err(HttpResponse::BadRequest().json(serde_json::json!({
            "error": "trigger.second must be between 0 and 59"
        })));
    }
    Ok(())
}

pub(super) async fn validate_auto_execute_run_config(
    state: &web::Data<AppState>,
    run_config: &ScheduleRunConfig,
) -> Result<(), HttpResponse> {
    if !run_config.auto_execute {
        return Ok(());
    }

    let has_task = run_config
        .task_message
        .as_deref()
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .is_some();

    if !has_task {
        return Err(HttpResponse::BadRequest().json(serde_json::json!({
            "error": "run_config.task_message is required when auto_execute is true"
        })));
    }

    let has_explicit_model = run_config
        .model
        .as_deref()
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .is_some();
    if has_explicit_model {
        return Ok(());
    }

    let snapshot = state.config.read().await.clone();
    if let Err(error) = get_default_model_from_config(&snapshot) {
        return Err(HttpResponse::BadRequest().json(serde_json::json!({
            "error": format!(
                "run_config.model not provided and no default model configured for provider {}: {}",
                snapshot.provider, error
            )
        })));
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::schedules::ScheduleWeekday;

    #[test]
    fn validate_schedule_name_accepts_valid_name() {
        let result = validate_schedule_name("Daily Backup");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "Daily Backup");
    }

    #[test]
    fn validate_schedule_name_trims_whitespace() {
        let result = validate_schedule_name("  Weekly Report  ");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "Weekly Report");
    }

    #[test]
    fn validate_schedule_name_rejects_empty_string() {
        let result = validate_schedule_name("");
        assert!(result.is_err());
    }

    #[test]
    fn validate_schedule_name_rejects_whitespace_only() {
        let result = validate_schedule_name("   ");
        assert!(result.is_err());
    }

    #[test]
    fn validate_schedule_name_accepts_unicode() {
        let result = validate_schedule_name("任务计划 🎯");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "任务计划 🎯");
    }

    #[test]
    fn validate_interval_trigger_rejects_zero_every_seconds() {
        let result = validate_schedule_trigger(&ScheduleTrigger::Interval {
            every_seconds: 0,
            anchor_at: None,
        });
        assert!(result.is_err());
    }

    #[test]
    fn resolve_create_schedule_definition_accepts_interval_trigger() {
        let req = CreateScheduleRequest {
            name: "interval".to_string(),
            trigger: ScheduleTrigger::Interval {
                every_seconds: 120,
                anchor_at: None,
            },
            timezone: Some("Asia/Shanghai".to_string()),
            start_at: None,
            end_at: None,
            misfire_policy: None,
            overlap_policy: None,
            enabled: false,
            run_config: ScheduleRunConfig::default(),
        };
        let resolved = resolve_create_schedule_definition(&req).unwrap();
        assert!(matches!(
            resolved.definition.trigger,
            Some(ScheduleTrigger::Interval {
                every_seconds: 120,
                ..
            })
        ));
        assert_eq!(
            resolved.definition.timezone.as_deref(),
            Some("Asia/Shanghai")
        );
    }

    #[test]
    fn resolve_create_schedule_definition_accepts_daily_trigger() {
        let req = CreateScheduleRequest {
            name: "daily".to_string(),
            trigger: ScheduleTrigger::Daily {
                hour: 9,
                minute: 0,
                second: 0,
            },
            timezone: Some("Asia/Shanghai".to_string()),
            start_at: None,
            end_at: None,
            misfire_policy: None,
            overlap_policy: None,
            enabled: false,
            run_config: ScheduleRunConfig::default(),
        };
        let resolved = resolve_create_schedule_definition(&req).unwrap();
        assert!(matches!(
            resolved.definition.trigger,
            Some(ScheduleTrigger::Daily {
                hour: 9,
                minute: 0,
                second: 0
            })
        ));
        assert_eq!(
            resolved.definition.timezone.as_deref(),
            Some("Asia/Shanghai")
        );
    }

    #[test]
    fn resolve_patch_schedule_definition_accepts_optional_trigger() {
        let req = PatchScheduleRequest {
            name: None,
            enabled: Some(true),
            trigger: Some(ScheduleTrigger::Cron {
                expr: "0 0 * * * *".to_string(),
            }),
            timezone: Some("UTC".to_string()),
            start_at: None,
            end_at: None,
            misfire_policy: None,
            overlap_policy: None,
            run_config: None,
        };
        let resolved = resolve_patch_schedule_definition(&req).unwrap();
        assert!(matches!(
            resolved.definition.trigger,
            Some(ScheduleTrigger::Cron { .. })
        ));
        assert_eq!(resolved.definition.timezone.as_deref(), Some("UTC"));
    }

    #[test]
    fn validate_schedule_window_rejects_inverted_range() {
        let start_at = DateTime::parse_from_rfc3339("2026-04-04T10:00:00Z")
            .unwrap()
            .with_timezone(&Utc);
        let end_at = DateTime::parse_from_rfc3339("2026-04-04T09:00:00Z")
            .unwrap()
            .with_timezone(&Utc);
        let response = validate_schedule_window(Some(start_at), Some(end_at))
            .expect_err("window should reject inverted range");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }

    #[test]
    fn validate_schedule_trigger_rejects_empty_weekdays() {
        let response = validate_schedule_trigger(&ScheduleTrigger::Weekly {
            weekdays: vec![],
            hour: 9,
            minute: 0,
            second: 0,
        })
        .expect_err("weekly trigger should require weekdays");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }

    #[test]
    fn validate_schedule_trigger_accepts_weekly_with_days() {
        validate_schedule_trigger(&ScheduleTrigger::Weekly {
            weekdays: vec![ScheduleWeekday::Mon],
            hour: 9,
            minute: 0,
            second: 0,
        })
        .expect("weekly trigger should accept weekdays");
    }

    #[test]
    fn validate_schedule_trigger_rejects_monthly_day_out_of_range() {
        let response = validate_schedule_trigger(&ScheduleTrigger::Monthly {
            days: vec![0, 32],
            hour: 9,
            minute: 0,
            second: 0,
        })
        .expect_err("monthly trigger should reject out-of-range days");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }

    #[test]
    fn validate_schedule_trigger_rejects_invalid_hour() {
        let response = validate_schedule_trigger(&ScheduleTrigger::Daily {
            hour: 24,
            minute: 0,
            second: 0,
        })
        .expect_err("daily trigger should reject invalid hour");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }

    #[test]
    fn validate_schedule_trigger_rejects_invalid_minute() {
        let response = validate_schedule_trigger(&ScheduleTrigger::Weekly {
            weekdays: vec![ScheduleWeekday::Tue],
            hour: 10,
            minute: 60,
            second: 0,
        })
        .expect_err("weekly trigger should reject invalid minute");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }

    #[test]
    fn validate_schedule_trigger_rejects_invalid_second() {
        let response = validate_schedule_trigger(&ScheduleTrigger::Monthly {
            days: vec![1],
            hour: 10,
            minute: 30,
            second: 60,
        })
        .expect_err("monthly trigger should reject invalid second");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }

    #[test]
    fn validate_schedule_trigger_rejects_blank_cron_expr() {
        let response = validate_schedule_trigger(&ScheduleTrigger::Cron {
            expr: "   ".to_string(),
        })
        .expect_err("cron trigger should reject blank expr");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }

    #[test]
    fn validate_trigger_api_fields_rejects_blank_timezone() {
        let response = validate_trigger_api_fields(
            Some(&ScheduleTrigger::Daily {
                hour: 9,
                minute: 0,
                second: 0,
            }),
            Some("   "),
            None,
            None,
            None,
            None,
        )
        .expect_err("blank timezone should be rejected");
        assert_eq!(response.status(), actix_web::http::StatusCode::BAD_REQUEST);
    }
}
