use chrono::{DateTime, NaiveDate, NaiveDateTime, TimeZone, Utc, Datelike};
use chrono_tz::Tz;
use serde::{Deserialize, Serialize};
use crate::error::{ApiError, Result};

pub const DEFAULT_CHURCH_TIMEZONE: &str = "America/New_York";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DateTimeWithTimezone {
    pub utc: DateTime<Utc>,
    pub local: String,
    pub timezone: String,
}

impl DateTimeWithTimezone {
    pub fn new(utc: DateTime<Utc>, timezone: &str) -> Result<Self> {
        let tz: Tz = timezone.parse()
            .map_err(|_| ApiError::BadRequest(format!("Invalid timezone: {}", timezone)))?;
        
        let local = utc.with_timezone(&tz);
        
        Ok(Self {
            utc,
            local: local.to_rfc3339(),
            timezone: timezone.to_string(),
        })
    }
    
    pub fn from_local_str(datetime_str: &str, timezone: &str) -> Result<Self> {
        let tz: Tz = timezone.parse()
            .map_err(|_| ApiError::BadRequest(format!("Invalid timezone: {}", timezone)))?;
        
        let naive_dt = if datetime_str.contains('T') {
            NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%dT%H:%M:%S")
                .or_else(|_| NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%dT%H:%M"))
                .map_err(|_| ApiError::BadRequest("Invalid datetime format. Use YYYY-MM-DDTHH:MM:SS or YYYY-MM-DDTHH:MM".to_string()))?
        } else {
            let date = NaiveDate::parse_from_str(datetime_str, "%Y-%m-%d")
                .map_err(|_| ApiError::BadRequest("Invalid date format. Use YYYY-MM-DD".to_string()))?;
            date.and_hms_opt(0, 0, 0)
                .ok_or_else(|| ApiError::BadRequest("Invalid time".to_string()))?
        };
        
        let local_dt = tz.from_local_datetime(&naive_dt)
            .single()
            .ok_or_else(|| ApiError::BadRequest("Ambiguous or invalid local time".to_string()))?;
        
        let utc = local_dt.with_timezone(&Utc);
        
        Ok(Self {
            utc,
            local: local_dt.to_rfc3339(),
            timezone: timezone.to_string(),
        })
    }
}

pub fn parse_date(date_str: &str) -> Result<NaiveDate> {
    NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
        .map_err(|_| ApiError::BadRequest("Invalid date format. Use YYYY-MM-DD".to_string()))
}


pub fn parse_datetime_with_timezone(
    datetime_str: &str,
    timezone: Option<&str>,
) -> Result<DateTimeWithTimezone> {
    let timezone = timezone.unwrap_or(DEFAULT_CHURCH_TIMEZONE);
    
    if datetime_str.ends_with('Z') || datetime_str.contains('+') || datetime_str.contains('-') {
        let dt = DateTime::parse_from_rfc3339(datetime_str)
            .map_err(|_| ApiError::BadRequest("Invalid RFC3339 datetime".to_string()))?;
        DateTimeWithTimezone::new(dt.with_timezone(&Utc), timezone)
    } else {
        DateTimeWithTimezone::from_local_str(datetime_str, timezone)
    }
}

/// Shared function for parsing datetime strings from event submissions
/// Converts local times (EST/EDT) to UTC for consistent database storage
/// Used by both V1 and V2 endpoints to ensure consistent timezone handling
pub fn parse_event_datetime_to_utc(datetime_str: &str) -> Result<DateTime<Utc>> {
    // Use the church's default timezone (EST/EDT) for conversion
    let parsed = parse_datetime_with_timezone(datetime_str, Some(DEFAULT_CHURCH_TIMEZONE))?;
    Ok(parsed.utc)
}




// ============================================================================
// V1/V2 Endpoint Timezone Conversion Utilities
// ============================================================================

/// Converts UTC datetime to EST/EDT for V1 endpoint backward compatibility
/// 
/// V1 clients expect EST times (even though they were labeled as UTC before).
/// After the database migration, we store proper UTC times but need to convert
/// them back to EST for V1 clients to maintain backward compatibility.
pub fn convert_utc_to_est_for_v1(utc_time: &DateTime<Utc>) -> Result<DateTime<Utc>> {
    let ny_tz: Tz = DEFAULT_CHURCH_TIMEZONE.parse()
        .map_err(|_| ApiError::BadRequest("Invalid default timezone".to_string()))?;
    
    // Convert UTC to EST/EDT (local New York time)
    let local_time = utc_time.with_timezone(&ny_tz);
    
    // Convert back to a DateTime<Utc> but with EST time values
    // This preserves the EST time values while keeping the Utc type for V1 compatibility
    let naive_local = local_time.naive_local();
    let est_as_utc = DateTime::from_naive_utc_and_offset(naive_local, Utc);
    
    Ok(est_as_utc)
}

/// Ensures that datetime input for database storage is converted to proper UTC
/// 
/// This function should be used when receiving datetime inputs to ensure they're
/// stored as proper UTC in the database after migration.

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Timelike;
    
    #[test]
    fn test_datetime_with_timezone() {
        let dt = DateTimeWithTimezone::from_local_str(
            "2025-07-15T14:30:00",
            "America/New_York"
        ).unwrap();
        
        assert_eq!(dt.timezone, "America/New_York");
        assert_eq!(dt.utc.hour(), 18);
    }
    
    #[test]
    fn test_convert_utc_to_est_for_v1() {
        // Test EDT (summer time): UTC-4
        let utc_time = Utc.with_ymd_and_hms(2025, 7, 15, 18, 30, 0).unwrap();
        let est_time = convert_utc_to_est_for_v1(&utc_time).unwrap();
        
        // Should be 14:30 (18:30 UTC - 4 hours for EDT)
        assert_eq!(est_time.hour(), 14);
        assert_eq!(est_time.minute(), 30);
    }
    
    #[test]
    fn test_convert_utc_to_est_for_v1_winter() {
        // Test EST (winter time): UTC-5  
        let utc_time = Utc.with_ymd_and_hms(2025, 1, 15, 19, 30, 0).unwrap();
        let est_time = convert_utc_to_est_for_v1(&utc_time).unwrap();
        
        // Should be 14:30 (19:30 UTC - 5 hours for EST)
        assert_eq!(est_time.hour(), 14);
        assert_eq!(est_time.minute(), 30);
    }
    
    #[test]
    fn test_ensure_utc_for_storage() {
        // Test converting EST input to UTC for storage
        let utc_time = ensure_utc_for_storage("2025-07-15T14:30:00", Some("America/New_York")).unwrap();
        
        // 14:30 EDT should become 18:30 UTC
        assert_eq!(utc_time.hour(), 18);
        assert_eq!(utc_time.minute(), 30);
    }
    
    #[test]
    fn test_prepare_utc_for_v2() {
        let utc_time = Utc.with_ymd_and_hms(2025, 7, 15, 18, 30, 0).unwrap();
        let result = prepare_utc_for_v2(&utc_time);
        
        // Should return the same UTC time unchanged
        assert_eq!(result, utc_time);
    }
}