use axum::{
    extract::{State, Path},
    http::StatusCode,
    response::IntoResponse,
    Json,
    Extension,
};
use serde::{Deserialize, Serialize};
use validator::Validate;
use mongodb::bson::{oid::ObjectId, DateTime};
use uuid::Uuid;

use crate::{
    auth::jwt::Claims,
    config::AppState,
    models::medication::{Medication, MedicationReminder, MedicationDose},
    models::audit_log::AuditEventType,
};

// ===== Request/Response Types =====

#[derive(Debug, Deserialize, Validate)]
pub struct CreateMedicationRequest {
    #[validate(length(min = 1))]
    pub profile_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reminders: Option<Vec<MedicationReminder>>,
    #[validate(length(min = 1))]
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dosage: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub frequency: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub instructions: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_date: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_date: Option<String>,
}

#[derive(Debug, Deserialize, Validate)]
pub struct UpdateMedicationRequest {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reminders: Option<Vec<MedicationReminder>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dosage: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub frequency: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub instructions: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_date: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_date: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct MedicationResponse {
    pub id: String,
    pub medication_id: String,
    pub user_id: String,
    pub profile_id: String,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dosage: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub frequency: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub instructions: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start_date: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end_date: Option<String>,
    pub reminders: Vec<MedicationReminder>,
    pub created_at: i64,
    pub updated_at: i64,
}

impl TryFrom<Medication> for MedicationResponse {
    type Error = anyhow::Error;
    
    fn try_from(med: Medication) -> Result<Self, Self::Error> {
        // Parse the encrypted medication data
        let data: serde_json::Value = serde_json::from_str(&med.medication_data.data)?;
        
        Ok(Self {
            id: med.id.map(|id| id.to_string()).unwrap_or_default(),
            medication_id: med.medication_id,
            user_id: med.user_id,
            profile_id: med.profile_id,
            name: data.get("name").and_then(|v| v.as_str()).unwrap_or("").to_string(),
            dosage: data.get("dosage").and_then(|v| v.as_str()).map(|s| s.to_string()),
            frequency: data.get("frequency").and_then(|v| v.as_str()).map(|s| s.to_string()),
            instructions: data.get("instructions").and_then(|v| v.as_str()).map(|s| s.to_string()),
            start_date: data.get("start_date").and_then(|v| v.as_str()).map(|s| s.to_string()),
            end_date: data.get("end_date").and_then(|v| v.as_str()).map(|s| s.to_string()),
            reminders: med.reminders,
            created_at: med.created_at.timestamp_millis(),
            updated_at: med.updated_at.timestamp_millis(),
        })
    }
}

#[derive(Debug, Deserialize, Validate)]
pub struct LogDoseRequest {
    pub taken: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scheduled_time: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub notes: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct LogDoseResponse {
    pub id: String,
    pub medication_id: String,
    pub logged_at: i64,
    pub taken: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub scheduled_time: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub notes: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct AdherenceResponse {
    pub total_doses: i32,
    pub taken_doses: i32,
    pub missed_doses: i32,
    pub adherence_percentage: f32,
}

// ===== Helper Functions =====

fn create_encrypted_field(data: &serde_json::Value) -> crate::models::health_data::EncryptedField {
    use crate::models::health_data::EncryptedField;
    
    // For now, we'll store the data as-is (not actually encrypted)
    // In production, this should be encrypted using the encryption service
    let json_str = serde_json::to_string(data).unwrap_or_default();
    
    EncryptedField {
        encrypted: false,
        data: json_str,
        iv: String::new(),
        auth_tag: String::new(),
    }
}

// ===== Handler Functions =====

pub async fn create_medication(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Json(req): Json<CreateMedicationRequest>,
) -> impl IntoResponse {
    if let Err(errors) = req.validate() {
        return (StatusCode::BAD_REQUEST, Json(serde_json::json!({
            "error": "validation failed",
            "details": errors.to_string()
        }))).into_response();
    }
    
    let medication_id = Uuid::new_v4().to_string();
    let now = DateTime::now();
    
    // Create medication data as JSON
    let mut medication_data = serde_json::json!({
        "name": req.name,
    });
    
    if let Some(dosage) = &req.dosage {
        medication_data["dosage"] = serde_json::json!(dosage);
    }
    if let Some(frequency) = &req.frequency {
        medication_data["frequency"] = serde_json::json!(frequency);
    }
    if let Some(instructions) = &req.instructions {
        medication_data["instructions"] = serde_json::json!(instructions);
    }
    if let Some(start_date) = &req.start_date {
        medication_data["start_date"] = serde_json::json!(start_date);
    }
    if let Some(end_date) = &req.end_date {
        medication_data["end_date"] = serde_json::json!(end_date);
    }
    
    let medication = Medication {
        id: None,
        medication_id: medication_id.clone(),
        user_id: claims.sub.clone(),
        profile_id: req.profile_id,
        medication_data: create_encrypted_field(&medication_data),
        reminders: req.reminders.unwrap_or_default(),
        created_at: now,
        updated_at: now,
    };
    
    match state.db.create_medication(&medication).await {
        Ok(Some(id)) => {
            // Log the creation
            if let Some(ref audit) = state.audit_logger {
                let user_id = ObjectId::parse_str(&claims.sub).ok();
                let _ = audit.log_event(
                    AuditEventType::DataModified,
                    user_id,
                    Some(claims.email.clone()),
                    "0.0.0.0".to_string(),
                    Some("medication".to_string()),
                    Some(id.to_string()),
                ).await;
            }
            
            let mut response_med = medication;
            response_med.id = Some(id);
            let response: MedicationResponse = response_med.try_into().unwrap();
            
            (StatusCode::CREATED, Json(response)).into_response()
        }
        Ok(None) => {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "failed to create medication"
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to create medication: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
}

pub async fn list_medications(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
) -> impl IntoResponse {
    match state.db.list_medications(&claims.sub, None).await {
        Ok(medications) => {
            let responses: Result<Vec<MedicationResponse>, _> = medications
                .into_iter()
                .map(|m| m.try_into())
                .collect();
            
            match responses {
                Ok(meds) => (StatusCode::OK, Json(meds)).into_response(),
                Err(e) => {
                    tracing::error!("Failed to convert medications: {}", e);
                    (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                        "error": "failed to process medications"
                    }))).into_response()
                }
            }
        }
        Err(e) => {
            tracing::error!("Failed to list medications: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
}

pub async fn get_medication(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Path(id): Path<String>,
) -> impl IntoResponse {
    // First verify user owns this medication
    match state.db.get_medication(&id).await {
        Ok(Some(medication)) => {
            if medication.user_id != claims.sub {
                return (StatusCode::FORBIDDEN, Json(serde_json::json!({
                    "error": "access denied"
                }))).into_response();
            }
            
            match MedicationResponse::try_from(medication) {
                Ok(response) => (StatusCode::OK, Json(response)).into_response(),
                Err(e) => {
                    tracing::error!("Failed to convert medication: {}", e);
                    (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                        "error": "failed to process medication"
                    }))).into_response()
                }
            }
        }
        Ok(None) => {
            (StatusCode::NOT_FOUND, Json(serde_json::json!({
                "error": "medication not found"
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get medication: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
}

pub async fn update_medication(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Path(id): Path<String>,
    Json(req): Json<UpdateMedicationRequest>,
) -> impl IntoResponse {
    if let Err(errors) = req.validate() {
        return (StatusCode::BAD_REQUEST, Json(serde_json::json!({
            "error": "validation failed",
            "details": errors.to_string()
        }))).into_response();
    }
    
    // First verify user owns this medication
    let mut medication = match state.db.get_medication(&id).await {
        Ok(Some(med)) => {
            if med.user_id != claims.sub {
                return (StatusCode::FORBIDDEN, Json(serde_json::json!({
                    "error": "access denied"
                }))).into_response();
            }
            med
        }
        Ok(None) => {
            return (StatusCode::NOT_FOUND, Json(serde_json::json!({
                "error": "medication not found"
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get medication: {}", e);
            return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    };
    
    // Parse existing data
    let mut existing_data: serde_json::Value = serde_json::from_str(&medication.medication_data.data).unwrap_or_default();
    
    // Update fields
    if let Some(name) = req.name {
        existing_data["name"] = serde_json::json!(name);
    }
    if let Some(dosage) = req.dosage {
        existing_data["dosage"] = serde_json::json!(dosage);
    }
    if let Some(frequency) = req.frequency {
        existing_data["frequency"] = serde_json::json!(frequency);
    }
    if let Some(instructions) = req.instructions {
        existing_data["instructions"] = serde_json::json!(instructions);
    }
    if let Some(start_date) = req.start_date {
        existing_data["start_date"] = serde_json::json!(start_date);
    }
    if let Some(end_date) = req.end_date {
        existing_data["end_date"] = serde_json::json!(end_date);
    }
    
    medication.medication_data = create_encrypted_field(&existing_data);
    medication.updated_at = DateTime::now();
    
    if let Some(reminders) = req.reminders {
        medication.reminders = reminders;
    }
    
    match state.db.update_medication(&medication).await {
        Ok(_) => {
            // Log the update
            if let Some(ref audit) = state.audit_logger {
                let user_id = ObjectId::parse_str(&claims.sub).ok();
                let _ = audit.log_event(
                    AuditEventType::DataModified,
                    user_id,
                    Some(claims.email.clone()),
                    "0.0.0.0".to_string(),
                    Some("medication".to_string()),
                    Some(id.clone()),
                ).await;
            }
            
            match MedicationResponse::try_from(medication) {
                Ok(response) => (StatusCode::OK, Json(response)).into_response(),
                Err(e) => {
                    tracing::error!("Failed to convert medication: {}", e);
                    (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                        "error": "failed to process medication"
                    }))).into_response()
                }
            }
        }
        Err(e) => {
            tracing::error!("Failed to update medication: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
}

pub async fn delete_medication(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Path(id): Path<String>,
) -> impl IntoResponse {
    // First verify user owns this medication
    match state.db.get_medication(&id).await {
        Ok(Some(medication)) => {
            if medication.user_id != claims.sub {
                return (StatusCode::FORBIDDEN, Json(serde_json::json!({
                    "error": "access denied"
                }))).into_response();
            }
        }
        Ok(None) => {
            return (StatusCode::NOT_FOUND, Json(serde_json::json!({
                "error": "medication not found"
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get medication: {}", e);
            return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
    
    match state.db.delete_medication(&id).await {
        Ok(_) => {
            // Log the deletion
            if let Some(ref audit) = state.audit_logger {
                let user_id = ObjectId::parse_str(&claims.sub).ok();
                let _ = audit.log_event(
                    AuditEventType::DataModified,
                    user_id,
                    Some(claims.email.clone()),
                    "0.0.0.0".to_string(),
                    Some("medication".to_string()),
                    Some(id),
                ).await;
            }
            
            (StatusCode::NO_CONTENT, ()).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to delete medication: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
}

pub async fn log_dose(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Path(id): Path<String>,
    Json(req): Json<LogDoseRequest>,
) -> impl IntoResponse {
    if let Err(errors) = req.validate() {
        return (StatusCode::BAD_REQUEST, Json(serde_json::json!({
            "error": "validation failed",
            "details": errors.to_string()
        }))).into_response();
    }
    
    // Verify user owns this medication
    match state.db.get_medication(&id).await {
        Ok(Some(medication)) => {
            if medication.user_id != claims.sub {
                return (StatusCode::FORBIDDEN, Json(serde_json::json!({
                    "error": "access denied"
                }))).into_response();
            }
        }
        Ok(None) => {
            return (StatusCode::NOT_FOUND, Json(serde_json::json!({
                "error": "medication not found"
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get medication: {}", e);
            return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
    
    let dose = MedicationDose {
        id: None,
        medication_id: id.clone(),
        user_id: claims.sub.clone(),
        logged_at: DateTime::now(),
        scheduled_time: req.scheduled_time,
        taken: req.taken,
        notes: req.notes,
    };
    
    match state.db.log_medication_dose(&dose).await {
        Ok(Some(dose_id)) => {
            let response = LogDoseResponse {
                id: dose_id.to_string(),
                medication_id: id,
                logged_at: dose.logged_at.timestamp_millis(),
                taken: dose.taken,
                scheduled_time: dose.scheduled_time,
                notes: dose.notes,
            };
            
            (StatusCode::CREATED, Json(response)).into_response()
        }
        Ok(None) => {
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "failed to log dose"
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to log dose: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
}

pub async fn get_adherence(
    State(state): State<AppState>,
    Extension(claims): Extension<Claims>,
    Path(id): Path<String>,
) -> impl IntoResponse {
    // Verify user owns this medication
    match state.db.get_medication(&id).await {
        Ok(Some(medication)) => {
            if medication.user_id != claims.sub {
                return (StatusCode::FORBIDDEN, Json(serde_json::json!({
                    "error": "access denied"
                }))).into_response()
            }
        }
        Ok(None) => {
            return (StatusCode::NOT_FOUND, Json(serde_json::json!({
                "error": "medication not found"
            }))).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get medication: {}", e);
            return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
    
    // Calculate adherence for the last 30 days
    match state.db.get_medication_adherence(&id, 30).await {
        Ok(stats) => {
            let response = AdherenceResponse {
                total_doses: stats.total_doses,
                taken_doses: stats.taken_doses,
                missed_doses: stats.missed_doses,
                adherence_percentage: stats.adherence_percentage,
            };
            
            (StatusCode::OK, Json(response)).into_response()
        }
        Err(e) => {
            tracing::error!("Failed to get adherence: {}", e);
            (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
                "error": "database error"
            }))).into_response()
        }
    }
}
