use serde::{Deserialize, Serialize};
use mongodb::bson::{oid::ObjectId, DateTime, doc};
use mongodb::Collection;
use super::health_data::EncryptedField;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Medication {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<ObjectId>,
    #[serde(rename = "medicationId")]
    pub medication_id: String,
    #[serde(rename = "userId")]
    pub user_id: String,
    #[serde(rename = "profileId")]
    pub profile_id: String,
    #[serde(rename = "medicationData")]
    pub medication_data: EncryptedField,
    #[serde(rename = "reminders")]
    pub reminders: Vec<MedicationReminder>,
    #[serde(rename = "createdAt")]
    pub created_at: DateTime,
    #[serde(rename = "updatedAt")]
    pub updated_at: DateTime,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MedicationReminder {
    #[serde(rename = "reminderId")]
    pub reminder_id: String,
    #[serde(rename = "scheduledTime")]
    pub scheduled_time: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MedicationDose {
    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
    pub id: Option<ObjectId>,
    #[serde(rename = "medicationId")]
    pub medication_id: String,
    #[serde(rename = "userId")]
    pub user_id: String,
    #[serde(rename = "loggedAt")]
    pub logged_at: DateTime,
    #[serde(rename = "scheduledTime")]
    pub scheduled_time: Option<String>,
    #[serde(rename = "taken")]
    pub taken: bool,
    #[serde(rename = "notes")]
    pub notes: Option<String>,
}

/// Repository for Medication operations
#[derive(Clone)]
pub struct MedicationRepository {
    collection: Collection<Medication>,
    dose_collection: Collection<MedicationDose>,
}

impl MedicationRepository {
    pub fn new(collection: Collection<Medication>, dose_collection: Collection<MedicationDose>) -> Self {
        Self { collection, dose_collection }
    }
    
    /// Create a new medication
    pub async fn create(&self, medication: &Medication) -> mongodb::error::Result<Option<ObjectId>> {
        let result = self.collection.insert_one(medication, None).await?;
        Ok(Some(result.inserted_id.as_object_id().unwrap()))
    }
    
    /// Find a medication by ID
    pub async fn find_by_id(&self, id: &ObjectId) -> mongodb::error::Result<Option<Medication>> {
        self.collection.find_one(doc! { "_id": id }, None).await
    }
    
    /// Find all medications for a user
    pub async fn find_by_user(&self, user_id: &str) -> mongodb::error::Result<Vec<Medication>> {
        use futures::stream::TryStreamExt;
        
        self.collection
            .find(doc! { "userId": user_id }, None)
            .await?
            .try_collect()
            .await
            .map_err(|e| mongodb::error::Error::from(e))
    }
    
    /// Find medications for a user filtered by profile
    pub async fn find_by_user_and_profile(&self, user_id: &str, profile_id: &str) -> mongodb::error::Result<Vec<Medication>> {
        use futures::stream::TryStreamExt;
        
        self.collection
            .find(doc! { "userId": user_id, "profileId": profile_id }, None)
            .await?
            .try_collect()
            .await
            .map_err(|e| mongodb::error::Error::from(e))
    }
    
    /// Update a medication
    pub async fn update(&self, medication: &Medication) -> mongodb::error::Result<()> {
        if let Some(id) = &medication.id {
            self.collection.replace_one(doc! { "_id": id }, medication, None).await?;
        }
        Ok(())
    }
    
    /// Delete a medication
    pub async fn delete(&self, medication_id: &ObjectId) -> mongodb::error::Result<()> {
        self.collection.delete_one(doc! { "_id": medication_id }, None).await?;
        Ok(())
    }
    
    /// Log a dose
    pub async fn log_dose(&self, dose: &MedicationDose) -> mongodb::error::Result<Option<ObjectId>> {
        let result = self.dose_collection.insert_one(dose, None).await?;
        Ok(Some(result.inserted_id.as_object_id().unwrap()))
    }
    
    /// Get doses for a medication
    pub async fn get_doses(&self, medication_id: &str, limit: Option<i64>) -> mongodb::error::Result<Vec<MedicationDose>> {
        use futures::stream::TryStreamExt;
        use mongodb::options::FindOptions;
        
        let opts = if let Some(limit) = limit {
            FindOptions::builder()
                .sort(doc! { "loggedAt": -1 })
                .limit(limit)
                .build()
        } else {
            FindOptions::builder()
                .sort(doc! { "loggedAt": -1 })
                .build()
        };
        
        self.dose_collection
            .find(doc! { "medicationId": medication_id }, opts)
            .await?
            .try_collect()
            .await
            .map_err(|e| mongodb::error::Error::from(e))
    }
    
    /// Calculate adherence for a medication
    pub async fn calculate_adherence(&self, medication_id: &str, days: i64) -> mongodb::error::Result<AdherenceStats> {
        use futures::stream::TryStreamExt;
        
        // Calculate the timestamp for 'days' ago
        let now = DateTime::now();
        let now_millis = now.timestamp_millis();
        let since_millis = now_millis - (days * 24 * 60 * 60 * 1000);
        let since = DateTime::from_millis(since_millis);
        
        let doses = self.dose_collection
            .find(
                doc! {
                    "medicationId": medication_id,
                    "loggedAt": { "$gte": since }
                },
                None,
            )
            .await?
            .try_collect::<Vec<MedicationDose>>()
            .await
            .map_err(|e| mongodb::error::Error::from(e))?;
        
        let total = doses.len() as i32;
        let taken = doses.iter().filter(|d| d.taken).count() as i32;
        let percentage = if total > 0 {
            (taken as f32 / total as f32) * 100.0
        } else {
            100.0
        };
        
        Ok(AdherenceStats {
            total_doses: total,
            taken_doses: taken,
            missed_doses: total - taken,
            adherence_percentage: percentage,
        })
    }
}

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