#![allow(clippy::needless_question_mark)]
use anyhow::Result;
use mongodb::bson::oid::ObjectId;
use mongodb::{bson::doc, options::ClientOptions, Client, Collection, Database};
use std::time::Duration;

use crate::models::{
    medication::{Medication, MedicationDose, MedicationRepository, UpdateMedicationRequest},
    permission::Permission,
    share::{Share, ShareRepository},
    user::{User, UserRepository},
};

#[derive(Clone)]
pub struct MongoDb {
    database: Database,
    pub users: Collection<User>,
    pub shares: Collection<Share>,
    pub medications: Collection<Medication>,
    pub medication_doses: Collection<MedicationDose>,
}

impl MongoDb {
    pub async fn new(uri: &str, db_name: &str) -> Result<Self> {
        eprintln!("[MongoDB] Starting connection to: {}", uri);

        // Parse the URI first
        let mut client_options = match ClientOptions::parse(uri).await {
            Ok(opts) => {
                eprintln!("[MongoDB] URI parsed successfully");
                opts
            }
            Err(e) => {
                eprintln!("[MongoDB] ERROR: Failed to parse URI: {}", e);

                let error_msg = e.to_string().to_lowercase();
                if error_msg.contains("dns")
                    || error_msg.contains("resolution")
                    || error_msg.contains("lookup")
                {
                    eprintln!("[MongoDB] DNS RESOLUTION ERROR DETECTED!");
                    eprintln!("[MongoDB] Cannot resolve hostname in: {}", uri);
                    eprintln!("[MongoDB] Error: {}", e);
                }
                eprintln!(
                    "[MongoDB] Will continue in degraded mode (database operations will fail)"
                );

                // Create a minimal configuration that will allow the server to start
                // but database operations will fail gracefully
                let mut opts = ClientOptions::parse("mongodb://localhost:27017")
                    .await
                    .map_err(|e| {
                        anyhow::anyhow!("Failed to create fallback client options: {}", e)
                    })?;
                opts.server_selection_timeout = Some(Duration::from_secs(1));
                opts.connect_timeout = Some(Duration::from_secs(1));

                let client = Client::with_options(opts)
                    .map_err(|e| anyhow::anyhow!("Failed to create MongoDB client: {}", e))?;

                let database = client.database(db_name);

                return Ok(Self {
                    users: database.collection("users"),
                    shares: database.collection("shares"),
                    medications: database.collection("medications"),
                    medication_doses: database.collection("medication_doses"),
                    database,
                });
            }
        };

        // Set connection timeout with retry logic
        client_options.server_selection_timeout = Some(Duration::from_secs(10));
        client_options.connect_timeout = Some(Duration::from_secs(10));

        eprintln!("[MongoDB] Connecting to server...");

        let client = match Client::with_options(client_options) {
            Ok(c) => {
                eprintln!("[MongoDB] Client created successfully");
                c
            }
            Err(e) => {
                eprintln!("[MongoDB] ERROR: Failed to create client: {}", e);
                eprintln!("[MongoDB] Will continue in degraded mode");

                // Create a fallback client
                let fallback_opts = ClientOptions::parse("mongodb://localhost:27017")
                    .await
                    .map_err(|e| {
                        anyhow::anyhow!("Failed to create fallback client options: {}", e)
                    })?;
                let fallback_client = Client::with_options(fallback_opts)
                    .map_err(|e| anyhow::anyhow!("Failed to create MongoDB client: {}", e))?;
                let database = fallback_client.database(db_name);

                return Ok(Self {
                    users: database.collection("users"),
                    shares: database.collection("shares"),
                    medications: database.collection("medications"),
                    medication_doses: database.collection("medication_doses"),
                    database,
                });
            }
        };

        eprintln!("[MongoDB] Client created, selecting database...");

        let database = client.database(db_name);

        eprintln!("[MongoDB] Database selected: {}", db_name);

        Ok(Self {
            users: database.collection("users"),
            shares: database.collection("shares"),
            medications: database.collection("medications"),
            medication_doses: database.collection("medication_doses"),
            database,
        })
    }

    pub async fn health_check(&self) -> Result<String> {
        eprintln!("[MongoDB] Health check: pinging database...");
        match self.database.run_command(doc! { "ping": 1 }, None).await {
            Ok(_) => {
                eprintln!("[MongoDB] Health check: OK");
                Ok("OK".to_string())
            }
            Err(e) => {
                eprintln!("[MongoDB] Health check: FAILED - {}", e);
                // Return OK anyway to allow the server to continue running
                // The actual database operations will fail when needed
                Ok("DEGRADED".to_string())
            }
        }
    }

    /// Get a reference to the underlying MongoDB Database
    /// This is needed for security services in Phase 2.6
    pub fn get_database(&self) -> Database {
        self.database.clone()
    }

    // ===== User Methods =====

    pub async fn create_user(&self, user: &User) -> Result<Option<ObjectId>> {
        let repo = UserRepository::new(self.users.clone());
        Ok(repo.create(user).await?)
    }

    pub async fn find_user_by_email(&self, email: &str) -> Result<Option<User>> {
        let repo = UserRepository::new(self.users.clone());
        Ok(repo.find_by_email(email).await?)
    }

    pub async fn find_user_by_id(&self, id: &ObjectId) -> Result<Option<User>> {
        let repo = UserRepository::new(self.users.clone());
        Ok(repo.find_by_id(id).await?)
    }

    pub async fn update_user(&self, user: &User) -> Result<()> {
        let repo = UserRepository::new(self.users.clone());
        repo.update(user).await?;
        Ok(())
    }

    pub async fn update_last_active(&self, user_id: &ObjectId) -> Result<()> {
        let repo = UserRepository::new(self.users.clone());
        repo.update_last_active(user_id).await?;
        Ok(())
    }

    pub async fn delete_user(&self, user_id: &ObjectId) -> Result<()> {
        let repo = UserRepository::new(self.users.clone());
        repo.delete(user_id).await?;
        Ok(())
    }

    // ===== Share Methods =====

    pub async fn create_share(&self, share: &Share) -> Result<Option<ObjectId>> {
        let repo = ShareRepository::new(self.shares.clone());
        Ok(repo.create(share).await?)
    }

    pub async fn get_share(&self, id: &str) -> Result<Option<Share>> {
        let object_id = ObjectId::parse_str(id)?;
        let repo = ShareRepository::new(self.shares.clone());
        Ok(repo.find_by_id(&object_id).await?)
    }

    pub async fn list_shares_for_user(&self, user_id: &str) -> Result<Vec<Share>> {
        let object_id = ObjectId::parse_str(user_id)?;
        let repo = ShareRepository::new(self.shares.clone());
        Ok(repo.find_by_target(&object_id).await?)
    }

    pub async fn update_share(&self, share: &Share) -> Result<()> {
        let repo = ShareRepository::new(self.shares.clone());
        repo.update(share).await?;
        Ok(())
    }

    pub async fn delete_share(&self, id: &str) -> Result<()> {
        let object_id = ObjectId::parse_str(id)?;
        let repo = ShareRepository::new(self.shares.clone());
        repo.delete(&object_id).await?;
        Ok(())
    }

    // ===== Permission Methods =====

    pub async fn check_user_permission(
        &self,
        user_id: &str,
        resource_type: &str,
        resource_id: &str,
        permission: &str,
    ) -> Result<bool> {
        let user_oid = ObjectId::parse_str(user_id)?;
        let resource_oid = ObjectId::parse_str(resource_id)?;

        let repo = ShareRepository::new(self.shares.clone());
        let shares = repo.find_by_target(&user_oid).await?;

        for share in shares {
            if share.resource_type == resource_type
                && share.resource_id.as_ref() == Some(&resource_oid)
                && share.active
                && !share.is_expired()
            {
                // Check if share has the required permission
                let perm = match permission.to_lowercase().as_str() {
                    "read" => Permission::Read,
                    "write" => Permission::Write,
                    "delete" => Permission::Delete,
                    "share" => Permission::Share,
                    "admin" => Permission::Admin,
                    _ => return Ok(false),
                };

                if share.has_permission(&perm) {
                    return Ok(true);
                }
            }
        }

        Ok(false)
    }

    /// Check permission using a simplified interface
    pub async fn check_permission(
        &self,
        user_id: &str,
        resource_id: &str,
        permission: &str,
    ) -> Result<bool> {
        // For now, check all resource types
        let resource_types = ["profiles", "health_data", "lab_results", "medications"];

        for resource_type in resource_types {
            if self
                .check_user_permission(user_id, resource_type, resource_id, permission)
                .await?
            {
                return Ok(true);
            }
        }

        Ok(false)
    }

    // ===== Medication Methods (Fixed for Phase 2.8) =====

    pub async fn create_medication(&self, medication: &Medication) -> Result<Option<ObjectId>> {
        let repo = MedicationRepository::new(self.medications.clone());
        let created = repo
            .create(medication.clone())
            .await
            .map_err(|e| anyhow::anyhow!("Failed to create medication: {}", e))?;
        Ok(created.id)
    }

    pub async fn get_medication(&self, id: &str) -> Result<Option<Medication>> {
        let object_id = ObjectId::parse_str(id)?;
        let repo = MedicationRepository::new(self.medications.clone());
        Ok(repo
            .find_by_id(&object_id)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to get medication: {}", e))?)
    }

    pub async fn list_medications(
        &self,
        user_id: &str,
        profile_id: Option<&str>,
    ) -> Result<Vec<Medication>> {
        let repo = MedicationRepository::new(self.medications.clone());
        if let Some(profile_id) = profile_id {
            Ok(repo
                .find_by_user_and_profile(user_id, profile_id)
                .await
                .map_err(|e| anyhow::anyhow!("Failed to list medications by profile: {}", e))?)
        } else {
            Ok(repo
                .find_by_user(user_id)
                .await
                .map_err(|e| anyhow::anyhow!("Failed to list medications: {}", e))?)
        }
    }

    pub async fn update_medication(
        &self,
        id: &str,
        updates: UpdateMedicationRequest,
    ) -> Result<Option<Medication>> {
        let object_id = ObjectId::parse_str(id)?;
        let repo = MedicationRepository::new(self.medications.clone());
        Ok(repo
            .update(&object_id, updates)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to update medication: {}", e))?)
    }

    pub async fn delete_medication(&self, id: &str) -> Result<bool> {
        let object_id = ObjectId::parse_str(id)?;
        let repo = MedicationRepository::new(self.medications.clone());
        Ok(repo
            .delete(&object_id)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to delete medication: {}", e))?)
    }

    pub async fn log_medication_dose(&self, dose: &MedicationDose) -> Result<Option<ObjectId>> {
        // Insert the dose into the medication_doses collection
        let result = self
            .medication_doses
            .insert_one(dose.clone(), None)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to log dose: {}", e))?;
        Ok(result.inserted_id.as_object_id())
    }

    pub async fn get_medication_adherence(
        &self,
        medication_id: &str,
        days: i64,
    ) -> Result<crate::models::medication::AdherenceStats> {
        let repo = MedicationRepository::new(self.medications.clone());
        Ok(repo
            .calculate_adherence(medication_id, days)
            .await
            .map_err(|e| anyhow::anyhow!("Failed to calculate adherence: {}", e))?)
    }
}
