use std::path::PathBuf;

use chrono::{DateTime, Local};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{
    cipher::{Cipher, CipherKind, EncryptedContent},
    env::{Env, EnvMap},
    error::Result,
    utils::save_serialized_profile,
};

#[derive(Clone, Serialize, Deserialize)]
pub struct ProfileMetadata {
    pub uuid: String,
    pub name: String,
    pub version: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    pub cipher_kind: CipherKind,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cipher_metadata: Option<serde_json::Value>,
    pub created_at: DateTime<Local>,
    pub updated_at: DateTime<Local>,
}

#[derive(Clone)]
pub struct Profile {
    pub metadata: ProfileMetadata,
    pub file_path: PathBuf,
    pub envs: EnvMap,
    pub cipher: Box<dyn Cipher>,
}

#[derive(Serialize, Deserialize)]
pub struct SerializedProfile {
    pub metadata: ProfileMetadata,
    pub content: EncryptedContent,
}

impl Profile {
    pub fn new(
        name: String,
        description: Option<String>,
        file_path: impl Into<PathBuf>,
        envs: EnvMap,
        cipher: Box<dyn Cipher>,
    ) -> Self {
        Self {
            metadata: ProfileMetadata {
                uuid: Uuid::new_v4().to_string(),
                name,
                version: env!("CARGO_PKG_VERSION").to_string(),
                description,
                cipher_kind: cipher.kind(),
                cipher_metadata: cipher.export_metadata(),
                created_at: Local::now(),
                updated_at: Local::now(),
            },
            file_path: file_path.into(),
            envs,
            cipher,
        }
    }

    pub fn expired_envs(&self) -> Vec<&Env> {
        self.envs.iter().filter(|env| env.is_expired()).collect()
    }

    pub fn save(&mut self) -> Result<()> {
        let encrypted_envs = self.cipher.encrypt(&self.envs)?;

        self.metadata.updated_at = Local::now();
        self.metadata.cipher_metadata = self.cipher.export_metadata();

        let serialized_profile = SerializedProfile {
            metadata: self.metadata.clone(),
            content: encrypted_envs,
        };

        save_serialized_profile(&self.file_path, serialized_profile)?;

        Ok(())
    }
}
