use crate::domain::jot::Jot;
use crate::domain::record_id::RecordId;
use crate::ports::storage::StoragePort;
use crate::ports::repository::{JotRepositoryPort, VaultRepositoryPort};
use crate::ports::id_generator::IdGeneratorPort;
use crate::error::{Result, JottamError};
use chrono::Utc;

pub struct JotUseCase<S: StoragePort, R: JotRepositoryPort, V: VaultRepositoryPort, G: IdGeneratorPort> {
    storage: S,
    jot_repo: R,
    vault_repo: V,
    id_gen: G,
}

impl<S: StoragePort, R: JotRepositoryPort, V: VaultRepositoryPort, G: IdGeneratorPort> JotUseCase<S, R, V, G> {
    pub fn new(storage: S, jot_repo: R, vault_repo: V, id_gen: G) -> Self {
        Self { storage, jot_repo, vault_repo, id_gen }
    }

    pub async fn create_jot(
        &self, 
        encrypted_file_data: &[u8], 
        encrypted_metadata: Vec<u8>,
        salt: Vec<u8>,
        owner_id: RecordId
    ) -> Result<Jot> {
        let jot_id = self.id_gen.generate();
        
        let storage_url = self.storage.upload_file(encrypted_file_data, Some(&owner_id.0)).await?;

        let now = Utc::now();
        let jot = Jot {
            id: jot_id,
            file_storage_url: storage_url,
            encrypted_file_metadata: encrypted_metadata,
            salt,
            owner_id,
            vault_id: None,
            created_at: now,
            updated_at: now,
            version: 1,
            is_multipart: false,
            chunk_size: None,
        };

        self.jot_repo.save(jot.clone()).await?;

        Ok(jot)
    }

    pub async fn get_jot(&self, id: &RecordId, owner_id: &RecordId) -> Result<Option<Jot>> {
        let jot = self.jot_repo.find_by_id(id).await?;
        if let Some(ref j) = jot {
            if &j.owner_id != owner_id {
                return Err(JottamError::Unauthorized("Unauthorized access to Jot".to_string()));
            }
        }
        Ok(jot)
    }

    pub async fn get_jot_by_id(&self, id: &RecordId) -> Result<Option<Jot>> {
        self.jot_repo.find_by_id(id).await
    }

    pub async fn list_jots_by_user(&self, owner_id: &RecordId, page: u32, limit: u32) -> Result<(Vec<Jot>, u64)> {
        let jots = self.jot_repo.find_by_user_id(owner_id, page, limit).await?;
        let total = self.jot_repo.count_by_user_id(owner_id).await?;
        Ok((jots, total))
    }

    pub async fn list_jots_by_vault(&self, vault_id: &RecordId, owner_id: &RecordId, page: u32, limit: u32) -> Result<(Vec<Jot>, u64)> {
        let vault = self.vault_repo.find_by_id(vault_id).await?
            .ok_or_else(|| JottamError::NotFound("Vault not found".to_string()))?;
        if &vault.user_id != owner_id {
            return Err(JottamError::Unauthorized("Unauthorized to access this Vault".to_string()));
        }
        let jots = self.jot_repo.find_by_vault_id_or_user_id(vault_id, owner_id, page, limit).await?;
        let total = self.jot_repo.count_by_vault_id(vault_id).await?;
        Ok((jots, total))
    }

    pub async fn delete_jot(&self, id: &RecordId, owner_id: &RecordId) -> Result<bool> {
        let jot = self.jot_repo.find_by_id(id).await?
            .ok_or_else(|| JottamError::NotFound("Jot not found".to_string()))?;
        if &jot.owner_id != owner_id {
            return Err(JottamError::Unauthorized("Unauthorized to delete this Jot".to_string()));
        }

        if let Some(vault_id) = jot.vault_id {
            if let Some(mut vault) = self.vault_repo.find_by_id(&vault_id).await? {
                vault.jot_ids.retain(|jid| jid != id);
                vault.updated_at = Utc::now();
                self.vault_repo.update(&vault_id, vault).await?;
            }
        }

        let deleted = self.jot_repo.delete(id).await?;
        if deleted {
            let _ = self.storage.delete_file(&jot.file_storage_url).await;
        }
        Ok(deleted)
    }

    pub async fn update_jot(
        &self,
        id: RecordId,
        owner_id: RecordId,
        encrypted_file_data: Vec<u8>,
        encrypted_metadata: Vec<u8>,
        salt: Option<Vec<u8>>,
        vault_id: Option<RecordId>,
    ) -> Result<Jot> {
        let mut jot = self.jot_repo.find_by_id(&id).await?
            .ok_or_else(|| JottamError::NotFound("Jot not found".to_string()))?;

        if jot.owner_id != owner_id {
            return Err(JottamError::Unauthorized("Unauthorized to update this Jot".to_string()));
        }

        if !encrypted_file_data.is_empty() {
            // Delete old file
            let _ = self.storage.delete_file(&jot.file_storage_url).await;
            // Upload new file
            jot.file_storage_url = self.storage.upload_file(&encrypted_file_data, Some(&owner_id.0)).await?;
        }

        if !encrypted_metadata.is_empty() {
            jot.encrypted_file_metadata = encrypted_metadata;
        }

        if let Some(s) = salt {
            jot.salt = s;
        }

        if jot.vault_id != vault_id {
            // Ensure the jot is disconnected from any previous vaults to maintain a strict 1:1 relationship
            self.vault_repo.remove_jot_from_all_vaults(&id, &owner_id).await?;
        }

        jot.vault_id = vault_id;
        jot.updated_at = Utc::now();
        jot.is_multipart = false; // Updates currently resets to single file if new file is provided
        jot.chunk_size = None;

        self.jot_repo.update(jot.clone()).await?;

        Ok(jot)
    }

    pub async fn get_jot_file_data(&self, jot: &Jot) -> Result<Vec<u8>> {
        self.storage.download_file(&jot.file_storage_url).await
    }

    pub async fn get_jot_download_url(&self, jot: &Jot, expires_in: std::time::Duration) -> Result<String> {
        self.storage.generate_presigned_url(&jot.file_storage_url, expires_in).await
    }

    pub async fn init_multipart_upload(&self, owner_id: &RecordId, num_parts: i32) -> Result<(String, String, Vec<(i32, String)>, RecordId)> {
        let jot_id = self.id_gen.generate();
        let storage_ref = format!("uploads/{}", uuid::Uuid::new_v4());
        
        let upload_id = self.storage.create_multipart_upload(&storage_ref, Some(&owner_id.0)).await?;
        
        let mut part_urls = Vec::with_capacity(num_parts as usize);
        for part_num in 1..=num_parts {
            let url = self.storage.generate_presigned_part_url(&storage_ref, &upload_id, part_num).await?;
            part_urls.push((part_num, url));
        }
        
        Ok((upload_id, storage_ref, part_urls, jot_id))
    }

    pub async fn complete_multipart_upload(
        &self,
        jot_id: RecordId,
        owner_id: RecordId,
        upload_id: &str,
        storage_ref: &str,
        parts: Vec<(i32, String)>,
        encrypted_metadata: Vec<u8>,
        salt: Vec<u8>,
        vault_id: Option<RecordId>,
    ) -> Result<Jot> {
        self.storage.complete_multipart_upload(storage_ref, upload_id, parts).await?;
        
        let now = Utc::now();
        let jot = Jot {
            id: jot_id,
            file_storage_url: storage_ref.to_string(),
            encrypted_file_metadata: encrypted_metadata,
            salt,
            owner_id,
            vault_id,
            created_at: now,
            updated_at: now,
            version: 1,
            is_multipart: true,
            chunk_size: Some(5 * 1024 * 1024), // We standardized on 5MB
        };

        self.jot_repo.save(jot.clone()).await?;

        Ok(jot)
    }
}
