use chrono::{Datelike, Duration, Local, NaiveDate};
use serde_json::json;

use super::contracts::{
    contradictory_period_mode_prompt, missing_period_mode_prompt, missing_period_prompt,
    missing_week_year_prompt, PeriodMode, ResolvedPeriod,
};

pub fn resolve_period(input: &str) -> Result<ResolvedPeriod, String> {
    let has_month = input.contains("月累计");
    let has_week = input.contains("周累计");

    match (has_month, has_week) {
        (true, true) => return Err(contradictory_period_mode_prompt()),
        (false, false) => return Err(missing_period_mode_prompt()),
        (true, false) => resolve_month_period(input),
        (false, true) => resolve_week_period(input),
    }
}

fn resolve_month_period(input: &str) -> Result<ResolvedPeriod, String> {
    if let Some(value) = extract_year_month_dash(input) {
        return Ok(ResolvedPeriod {
            mode: PeriodMode::Month,
            mode_code: "1".to_string(),
            value: value.clone(),
            payload: json!({ "fdate": value }),
        });
    }

    if let Some(value) = extract_year_month_cn(input) {
        return Ok(ResolvedPeriod {
            mode: PeriodMode::Month,
            mode_code: "1".to_string(),
            value: value.clone(),
            payload: json!({ "fdate": value }),
        });
    }

    if contains_explicit_month_period_hint(input) {
        return Err(missing_period_prompt());
    }

    Ok(default_month_period())
}

fn resolve_week_period(input: &str) -> Result<ResolvedPeriod, String> {
    if input.contains('第') && input.contains('周') && !input.contains('年') {
        return Err(missing_week_year_prompt());
    }

    if let Some((year, week)) = extract_year_week(input) {
        let Some(week_start) = week_start_date(year, week) else {
            return Err(missing_period_prompt());
        };
        let week_end = week_start + Duration::days(6);

        return Ok(ResolvedPeriod {
            mode: PeriodMode::Week,
            mode_code: "2".to_string(),
            value: format!("{year}-W{week:02}"),
            payload: json!({
                "tjzq": "week",
                "level": "00",
                "weekSfdate": week_start.format("%Y-%m-%d").to_string(),
                "weekEfdate": week_end.format("%Y-%m-%d").to_string(),
            }),
        });
    }

    if contains_explicit_week_period_hint(input) {
        return Err(missing_period_prompt());
    }

    Ok(default_week_period())
}

fn default_month_period() -> ResolvedPeriod {
    let today = Local::now().date_naive();
    let (year, month) = if today.month() == 1 {
        (today.year() - 1, 12)
    } else {
        (today.year(), today.month() - 1)
    };
    let value = format!("{year}-{month:02}");

    ResolvedPeriod {
        mode: PeriodMode::Month,
        mode_code: "1".to_string(),
        value: value.clone(),
        payload: json!({ "fdate": value }),
    }
}

fn default_week_period() -> ResolvedPeriod {
    let today = Local::now().date_naive();
    let month_start = today.with_day(1).expect("current month should have day 1");
    let start = month_start.format("%Y-%m-%d").to_string();
    let end = today.format("%Y-%m-%d").to_string();

    ResolvedPeriod {
        mode: PeriodMode::Week,
        mode_code: "2".to_string(),
        value: format!("{start}至{end}"),
        payload: json!({
            "tjzq": "week",
            "level": "00",
            "weekSfdate": start,
            "weekEfdate": end,
        }),
    }
}

fn contains_explicit_month_period_hint(input: &str) -> bool {
    let trimmed = input.replace("月累计", "");
    trimmed.contains('年')
        || trimmed.contains('月')
        || trimmed.contains('-')
        || trimmed.chars().any(|ch| ch.is_ascii_digit())
}

fn contains_explicit_week_period_hint(input: &str) -> bool {
    let trimmed = input.replace("周累计", "");
    trimmed.contains('年')
        || trimmed.contains('第')
        || trimmed.contains('周')
        || trimmed.contains('-')
        || trimmed.chars().any(|ch| ch.is_ascii_digit())
}

fn extract_year_month_dash(input: &str) -> Option<String> {
    let chars: Vec<char> = input.chars().collect();
    for window in chars.windows(7) {
        let candidate: String = window.iter().collect();
        if is_year_month_dash(&candidate) {
            return Some(candidate);
        }
    }
    None
}

fn is_year_month_dash(candidate: &str) -> bool {
    let bytes = candidate.as_bytes();
    bytes.len() == 7
        && bytes[0..4].iter().all(u8::is_ascii_digit)
        && bytes[4] == b'-'
        && bytes[5..7].iter().all(u8::is_ascii_digit)
        && matches!((bytes[5] - b'0') * 10 + (bytes[6] - b'0'), 1..=12)
}

fn extract_year_month_cn(input: &str) -> Option<String> {
    let chars: Vec<char> = input.chars().collect();
    for index in 0..chars.len() {
        if index + 6 >= chars.len() {
            break;
        }
        if !chars[index..index + 4].iter().all(|ch| ch.is_ascii_digit()) {
            continue;
        }
        if chars[index + 4] != '年' {
            continue;
        }

        let mut month_digits = String::new();
        let mut cursor = index + 5;
        while cursor < chars.len() && chars[cursor].is_ascii_digit() && month_digits.len() < 2 {
            month_digits.push(chars[cursor]);
            cursor += 1;
        }
        if month_digits.is_empty() || cursor >= chars.len() || chars[cursor] != '月' {
            continue;
        }

        let month: u32 = month_digits.parse().ok()?;
        if !(1..=12).contains(&month) {
            continue;
        }
        let year: String = chars[index..index + 4].iter().collect();
        return Some(format!("{year}-{month:02}"));
    }
    None
}

fn extract_year_week(input: &str) -> Option<(i32, u32)> {
    let chars: Vec<char> = input.chars().collect();
    for index in 0..chars.len() {
        if index + 7 >= chars.len() {
            break;
        }
        if !chars[index..index + 4].iter().all(|ch| ch.is_ascii_digit()) {
            continue;
        }
        if chars[index + 4] != '年' || chars[index + 5] != '第' {
            continue;
        }

        let mut week_digits = String::new();
        let mut cursor = index + 6;
        while cursor < chars.len() && chars[cursor].is_ascii_digit() && week_digits.len() < 2 {
            week_digits.push(chars[cursor]);
            cursor += 1;
        }
        if week_digits.is_empty() || cursor >= chars.len() || chars[cursor] != '周' {
            continue;
        }

        let year: i32 = chars[index..index + 4].iter().collect::<String>().parse().ok()?;
        let week: u32 = week_digits.parse().ok()?;
        if !(1..=53).contains(&week) {
            continue;
        }
        return Some((year, week));
    }
    None
}

fn week_start_date(year: i32, week: u32) -> Option<NaiveDate> {
    let jan4 = NaiveDate::from_ymd_opt(year, 1, 4)?;
    let iso_week1_monday = jan4 - Duration::days(jan4.weekday().num_days_from_monday() as i64);
    let candidate = iso_week1_monday + Duration::weeks((week - 1) as i64);
    let iso = candidate.iso_week();
    (iso.year() == year && iso.week() == week).then_some(candidate)
}

#[cfg(test)]
mod tests {
    use super::resolve_period;
    use crate::compat::tq_lineloss::contracts::PeriodMode;

    #[test]
    fn resolves_dash_month() {
        let resolved = resolve_period("月累计 2026-03").unwrap();
        assert_eq!(resolved.mode, PeriodMode::Month);
        assert_eq!(resolved.payload["fdate"], "2026-03");
    }

    #[test]
    fn resolves_week_range() {
        let resolved = resolve_period("周累计 2026年第12周").unwrap();
        assert_eq!(resolved.mode, PeriodMode::Week);
        assert_eq!(resolved.payload["weekSfdate"], "2026-03-16");
        assert_eq!(resolved.payload["weekEfdate"], "2026-03-22");
    }
}
