use axum::{
    extract::{Path, State},
    response::Json,
};
use chrono::{DateTime, Utc};
use ical::parser::ical::component::IcalEvent;
use reqwest::Client;
use serde::Serialize;
use std::sync::Arc;

use crate::{
    db::ExternalCalendarRepository,
    models::ApiError,
    AppState,
};

// Import VEvent from calendar-models shared crate
use calendar_models::VEvent;

use super::auth::{extract_bearer_token};

#[derive(Debug, Serialize)]
pub struct ExternalCalendarEventsResponse {
    pub events: Vec<VEvent>,
    pub last_fetched: DateTime<Utc>,
}

pub async fn fetch_external_calendar_events(
    headers: axum::http::HeaderMap,
    State(app_state): State<Arc<AppState>>,
    Path(id): Path<i32>,
) -> Result<Json<ExternalCalendarEventsResponse>, ApiError> {
    let token = extract_bearer_token(&headers)?;
    let user = app_state.auth_service.get_user_from_token(&token).await?;

    let repo = ExternalCalendarRepository::new(&app_state.db);
    
    // Get user's external calendars to verify ownership and get URL
    let calendars = repo
        .get_by_user(&user.id)
        .await
        .map_err(|e| ApiError::Database(format!("Failed to get external calendars: {}", e)))?;

    let calendar = calendars
        .into_iter()
        .find(|c| c.id == id)
        .ok_or_else(|| ApiError::NotFound("External calendar not found".to_string()))?;

    if !calendar.is_visible {
        return Ok(Json(ExternalCalendarEventsResponse {
            events: vec![],
            last_fetched: Utc::now(),
        }));
    }

    // Check cache first
    let cache_max_age_minutes = 5;
    let mut ics_content = String::new();
    let mut last_fetched = Utc::now();
    let mut fetched_from_cache = false;

    // Try to get from cache if not stale
    match repo.is_cache_stale(id, cache_max_age_minutes).await {
        Ok(is_stale) => {
            if !is_stale {
                // Cache is fresh, use it
                if let Ok(Some((cached_data, cached_at))) = repo.get_cached_data(id).await {
                    ics_content = cached_data;
                    last_fetched = cached_at;
                    fetched_from_cache = true;
                }
            }
        }
        Err(_) => {
            // If cache check fails, proceed to fetch from URL
        }
    }

    // If not fetched from cache, get from external URL
    if !fetched_from_cache {
        let client = Client::new();
        let response = client
            .get(&calendar.url)
            .send()
            .await
            .map_err(|e| ApiError::Internal(format!("Failed to fetch calendar: {}", e)))?;

        if !response.status().is_success() {
            return Err(ApiError::Internal(format!("Calendar server returned: {}", response.status())));
        }

        ics_content = response
            .text()
            .await
            .map_err(|e| ApiError::Internal(format!("Failed to read calendar content: {}", e)))?;

        // Store in cache for future requests
        let etag = None; // TODO: Extract ETag from response headers if available
        if let Err(_) = repo.update_cache(id, &ics_content, etag).await {
            // Log error but don't fail the request
        }

        // Update last_fetched timestamp
        if let Err(_) = repo.update_last_fetched(id, &user.id).await {
        }
        
        last_fetched = Utc::now();
    }

    // Parse ICS content
    let events = parse_ics_content(&ics_content)
        .map_err(|e| ApiError::BadRequest(format!("Failed to parse calendar: {}", e)))?;

    Ok(Json(ExternalCalendarEventsResponse {
        events,
        last_fetched,
    }))
}

fn parse_ics_content(ics_content: &str) -> Result<Vec<VEvent>, Box<dyn std::error::Error>> {
    let reader = ical::IcalParser::new(ics_content.as_bytes());
    let mut events = Vec::new();
    let mut _total_components = 0;
    let mut _failed_conversions = 0;

    for calendar in reader {
        let calendar = calendar?;
        for component in calendar.events {
            _total_components += 1;
            match convert_ical_to_vevent(component) {
                Ok(vevent) => {
                    events.push(vevent);
                }
                Err(_) => {
                    _failed_conversions += 1;
                }
            }
        }
    }


    Ok(events)
}

fn convert_ical_to_vevent(ical_event: IcalEvent) -> Result<VEvent, Box<dyn std::error::Error>> {
    use uuid::Uuid;

    let mut summary = None;
    let mut description = None;
    let mut location = None;
    let mut dtstart = None;
    let mut dtend = None;
    let mut uid = None;
    let mut all_day = false;
    let mut rrule = None;


    // Extract properties
    for property in ical_event.properties {
        match property.name.as_str() {
            "SUMMARY" => {
                summary = property.value;
            }
            "DESCRIPTION" => {
                description = property.value;
            }
            "LOCATION" => {
                location = property.value;
            }
            "DTSTART" => {
                if let Some(value) = property.value {
                    // Check if it's a date-only value (all-day event)
                    if value.len() == 8 && !value.contains('T') {
                        all_day = true;
                        // Parse YYYYMMDD format
                        if let Ok(date) = chrono::NaiveDate::parse_from_str(&value, "%Y%m%d") {
                            dtstart = Some(chrono::TimeZone::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap()));
                        }
                    } else {
                        // Extract timezone info from parameters
                        let tzid = property.params.as_ref()
                            .and_then(|params| params.iter().find(|(k, _)| k == "TZID"))
                            .and_then(|(_, v)| v.first().cloned());
                        
                        // Parse datetime with timezone information
                        if let Some(dt) = parse_datetime_with_tz(&value, tzid.as_deref()) {
                            dtstart = Some(dt);
                        }
                    }
                }
            }
            "DTEND" => {
                if let Some(value) = property.value {
                    if all_day && value.len() == 8 && !value.contains('T') {
                        // For all-day events, DTEND is exclusive so use the date as-is at noon
                        if let Ok(date) = chrono::NaiveDate::parse_from_str(&value, "%Y%m%d") {
                            dtend = Some(chrono::TimeZone::from_utc_datetime(&Utc, &date.and_hms_opt(12, 0, 0).unwrap()));
                        }
                    } else {
                        // Extract timezone info from parameters
                        let tzid = property.params.as_ref()
                            .and_then(|params| params.iter().find(|(k, _)| k == "TZID"))
                            .and_then(|(_, v)| v.first().cloned());
                        
                        // Parse datetime with timezone information
                        if let Some(dt) = parse_datetime_with_tz(&value, tzid.as_deref()) {
                            dtend = Some(dt);
                        }
                    }
                }
            }
            "UID" => {
                uid = property.value;
            }
            "RRULE" => {
                rrule = property.value;
            }
            _ => {} // Ignore other properties for now
        }
    }


    let dtstart = dtstart.ok_or("Missing DTSTART")?;

    let vevent = VEvent {
        uid: uid.unwrap_or_else(|| Uuid::new_v4().to_string()),
        dtstart,
        dtend,
        summary,
        description,
        location,
        all_day,
        rrule,
        exdate: Vec::new(), // External calendars don't need exception handling
        recurrence_id: None,
        created: None,
        last_modified: None,
        dtstamp: Utc::now(),
        sequence: Some(0),
        status: None,
        transp: None,
        organizer: None,
        attendees: Vec::new(),
        url: None,
        attachments: Vec::new(),
        categories: Vec::new(),
        priority: None,
        resources: Vec::new(),
        related_to: None,
        geo: None,
        duration: None,
        class: None,
        contact: None,
        comment: None,
        rdate: Vec::new(),
        alarms: Vec::new(),
        etag: None,
        href: None,
        calendar_path: None,
    };

    Ok(vevent)
}

fn parse_datetime_with_tz(datetime_str: &str, tzid: Option<&str>) -> Option<DateTime<Utc>> {
    use chrono::TimeZone;
    use chrono_tz::Tz;
    
    
    // Try various datetime formats commonly found in ICS files
    
    // Format: 20231201T103000Z (UTC) - handle as naive datetime first
    if datetime_str.ends_with('Z') {
        let datetime_without_z = &datetime_str[..datetime_str.len()-1];
        if let Ok(naive_dt) = chrono::NaiveDateTime::parse_from_str(datetime_without_z, "%Y%m%dT%H%M%S") {
            return Some(naive_dt.and_utc());
        }
    }

    // Format: 20231201T103000-0500 (with timezone offset)
    if let Ok(dt) = DateTime::parse_from_str(datetime_str, "%Y%m%dT%H%M%S%z") {
        return Some(dt.with_timezone(&Utc));
    }

    // Format: 2023-12-01T10:30:00Z (ISO format)
    if let Ok(dt) = DateTime::parse_from_str(datetime_str, "%Y-%m-%dT%H:%M:%SZ") {
        return Some(dt.with_timezone(&Utc));
    }

    // Handle naive datetime with timezone parameter
    let naive_dt = if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(datetime_str, "%Y%m%dT%H%M%S") {
        Some(dt)
    } else if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(datetime_str, "%Y-%m-%dT%H:%M:%S") {
        Some(dt)
    } else {
        None
    };

    if let Some(naive_dt) = naive_dt {
        // If TZID is provided, try to parse it
        if let Some(tzid_str) = tzid {
            // Handle common timezone formats
            let tz_result = if tzid_str.starts_with("/mozilla.org/") {
                // Mozilla/Thunderbird format: /mozilla.org/20070129_1/Europe/London
                tzid_str.split('/').last().and_then(|tz_name| tz_name.parse::<Tz>().ok())
            } else if tzid_str.contains('/') {
                // Standard timezone format: America/New_York, Europe/London
                tzid_str.parse::<Tz>().ok()
            } else {
                // Try common abbreviations and Windows timezone names
                match tzid_str {
                    // Standard abbreviations
                    "EST" => Some(Tz::America__New_York),
                    "PST" => Some(Tz::America__Los_Angeles),
                    "MST" => Some(Tz::America__Denver), 
                    "CST" => Some(Tz::America__Chicago),
                    
                    // North America - Windows timezone names to IANA mapping
                    "Mountain Standard Time" => Some(Tz::America__Denver),
                    "Eastern Standard Time" => Some(Tz::America__New_York),
                    "Central Standard Time" => Some(Tz::America__Chicago),
                    "Pacific Standard Time" => Some(Tz::America__Los_Angeles),
                    "Mountain Daylight Time" => Some(Tz::America__Denver),
                    "Eastern Daylight Time" => Some(Tz::America__New_York),
                    "Central Daylight Time" => Some(Tz::America__Chicago),
                    "Pacific Daylight Time" => Some(Tz::America__Los_Angeles),
                    "Hawaiian Standard Time" => Some(Tz::Pacific__Honolulu),
                    "Alaskan Standard Time" => Some(Tz::America__Anchorage),
                    "Alaskan Daylight Time" => Some(Tz::America__Anchorage),
                    "Atlantic Standard Time" => Some(Tz::America__Halifax),
                    "Newfoundland Standard Time" => Some(Tz::America__St_Johns),
                    
                    // Europe
                    "GMT Standard Time" => Some(Tz::Europe__London),
                    "Greenwich Standard Time" => Some(Tz::UTC),
                    "W. Europe Standard Time" => Some(Tz::Europe__Berlin),
                    "Central Europe Standard Time" => Some(Tz::Europe__Warsaw),
                    "Romance Standard Time" => Some(Tz::Europe__Paris),
                    "Central European Standard Time" => Some(Tz::Europe__Belgrade),
                    "E. Europe Standard Time" => Some(Tz::Europe__Bucharest),
                    "FLE Standard Time" => Some(Tz::Europe__Helsinki),
                    "GTB Standard Time" => Some(Tz::Europe__Athens),
                    "Russian Standard Time" => Some(Tz::Europe__Moscow),
                    "Turkey Standard Time" => Some(Tz::Europe__Istanbul),
                    
                    // Asia
                    "China Standard Time" => Some(Tz::Asia__Shanghai),
                    "Tokyo Standard Time" => Some(Tz::Asia__Tokyo),
                    "Korea Standard Time" => Some(Tz::Asia__Seoul),
                    "Singapore Standard Time" => Some(Tz::Asia__Singapore),
                    "India Standard Time" => Some(Tz::Asia__Kolkata),
                    "Pakistan Standard Time" => Some(Tz::Asia__Karachi),
                    "Bangladesh Standard Time" => Some(Tz::Asia__Dhaka),
                    "Thailand Standard Time" => Some(Tz::Asia__Bangkok),
                    "SE Asia Standard Time" => Some(Tz::Asia__Bangkok),
                    "Myanmar Standard Time" => Some(Tz::Asia__Yangon),
                    "Sri Lanka Standard Time" => Some(Tz::Asia__Colombo),
                    "Nepal Standard Time" => Some(Tz::Asia__Kathmandu),
                    "Central Asia Standard Time" => Some(Tz::Asia__Almaty),
                    "West Asia Standard Time" => Some(Tz::Asia__Tashkent),
                    "Afghanistan Standard Time" => Some(Tz::Asia__Kabul),
                    "Iran Standard Time" => Some(Tz::Asia__Tehran),
                    "Arabian Standard Time" => Some(Tz::Asia__Dubai),
                    "Arab Standard Time" => Some(Tz::Asia__Riyadh),
                    "Israel Standard Time" => Some(Tz::Asia__Jerusalem),
                    "Jordan Standard Time" => Some(Tz::Asia__Amman),
                    "Syria Standard Time" => Some(Tz::Asia__Damascus),
                    "Middle East Standard Time" => Some(Tz::Asia__Beirut),
                    "Egypt Standard Time" => Some(Tz::Africa__Cairo),
                    "South Africa Standard Time" => Some(Tz::Africa__Johannesburg),
                    "E. Africa Standard Time" => Some(Tz::Africa__Nairobi),
                    "W. Central Africa Standard Time" => Some(Tz::Africa__Lagos),
                    
                    // Asia Pacific
                    "AUS Eastern Standard Time" => Some(Tz::Australia__Sydney),
                    "AUS Central Standard Time" => Some(Tz::Australia__Darwin),
                    "W. Australia Standard Time" => Some(Tz::Australia__Perth),
                    "Tasmania Standard Time" => Some(Tz::Australia__Hobart),
                    "New Zealand Standard Time" => Some(Tz::Pacific__Auckland),
                    "Fiji Standard Time" => Some(Tz::Pacific__Fiji),
                    "Tonga Standard Time" => Some(Tz::Pacific__Tongatapu),
                    
                    // South America
                    "Argentina Standard Time" => Some(Tz::America__Buenos_Aires),
                    "E. South America Standard Time" => Some(Tz::America__Sao_Paulo),
                    "SA Eastern Standard Time" => Some(Tz::America__Cayenne),
                    "SA Pacific Standard Time" => Some(Tz::America__Bogota),
                    "SA Western Standard Time" => Some(Tz::America__La_Paz),
                    "Pacific SA Standard Time" => Some(Tz::America__Santiago),
                    "Venezuela Standard Time" => Some(Tz::America__Caracas),
                    "Montevideo Standard Time" => Some(Tz::America__Montevideo),
                    
                    // Try parsing as IANA name
                    _ => tzid_str.parse::<Tz>().ok()
                }
            };

            if let Some(tz) = tz_result {
                if let Some(dt_with_tz) = tz.from_local_datetime(&naive_dt).single() {
                    return Some(dt_with_tz.with_timezone(&Utc));
                }
            }
        }
        
        // If no timezone info or parsing failed, treat as UTC (safer than local time assumptions)
        return Some(chrono::TimeZone::from_utc_datetime(&Utc, &naive_dt));
    }

    None
}