use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// The main data model that mirrors the frontend's DataModel type.
/// This is sent via WebSocket as the initial state and updated via patches.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DataModel {
    #[serde(rename = "server-info")]
    pub server_info: ServerInfo,
    #[serde(rename = "package-data")]
    pub package_data: HashMap<String, PackageDataEntry>,
    pub ui: UIData,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ServerInfo {
    pub id: String,
    pub version: String,
    pub name: Option<String>,
    pub pubkey: String,
    #[serde(rename = "status-info")]
    pub status_info: StatusInfo,
    #[serde(rename = "lan-address")]
    pub lan_address: Option<String>,
    #[serde(rename = "tor-address")]
    pub tor_address: Option<String>,
    #[serde(rename = "node-address", skip_serializing_if = "Option::is_none")]
    pub node_address: Option<String>,
    pub unread: u32,
    #[serde(rename = "wifi-ssids")]
    pub wifi_ssids: Vec<String>,
    #[serde(rename = "zram-enabled")]
    pub zram_enabled: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StatusInfo {
    pub restarting: bool,
    #[serde(rename = "shutting-down")]
    pub shutting_down: bool,
    pub updated: bool,
    #[serde(rename = "backup-progress")]
    pub backup_progress: Option<f32>,
    #[serde(rename = "update-progress")]
    pub update_progress: Option<f32>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UIData {
    pub name: Option<String>,
    #[serde(rename = "ack-welcome")]
    pub ack_welcome: String,
    pub marketplace: UIMarketplaceData,
    pub theme: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UIMarketplaceData {
    #[serde(rename = "selected-hosts")]
    pub selected_hosts: Vec<String>,
    #[serde(rename = "known-hosts")]
    pub known_hosts: HashMap<String, MarketplaceHost>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MarketplaceHost {
    pub name: String,
    pub url: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum PackageState {
    Installing,
    Installed,
    Stopping,
    Stopped,
    Starting,
    Running,
    Restarting,
    #[serde(rename = "creating-backup")]
    CreatingBackup,
    #[serde(rename = "restoring-backup")]
    RestoringBackup,
    Removing,
    #[serde(rename = "backing-up")]
    BackingUp,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PackageDataEntry {
    pub state: PackageState,
    #[serde(rename = "static-files")]
    pub static_files: StaticFiles,
    pub manifest: Manifest,
    pub installed: Option<InstalledPackageDataEntry>,
    #[serde(rename = "install-progress")]
    pub install_progress: Option<InstallProgress>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct StaticFiles {
    pub license: String,
    pub instructions: String,
    pub icon: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Manifest {
    pub id: String,
    pub title: String,
    pub version: String,
    pub description: Description,
    #[serde(rename = "release-notes")]
    pub release_notes: String,
    pub license: String,
    #[serde(rename = "wrapper-repo")]
    pub wrapper_repo: String,
    #[serde(rename = "upstream-repo")]
    pub upstream_repo: String,
    #[serde(rename = "support-site")]
    pub support_site: String,
    #[serde(rename = "marketing-site")]
    pub marketing_site: String,
    #[serde(rename = "donation-url")]
    pub donation_url: Option<String>,
    pub author: Option<String>,
    pub website: Option<String>,
    pub interfaces: Option<Interfaces>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Description {
    pub short: String,
    pub long: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Interfaces {
    pub main: Option<MainInterface>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MainInterface {
    pub ui: Option<String>,
    #[serde(rename = "tor-config")]
    pub tor_config: Option<String>,
    #[serde(rename = "lan-config")]
    pub lan_config: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InstalledPackageDataEntry {
    #[serde(rename = "current-dependents")]
    pub current_dependents: HashMap<String, CurrentDependencyInfo>,
    #[serde(rename = "current-dependencies")]
    pub current_dependencies: HashMap<String, CurrentDependencyInfo>,
    #[serde(rename = "last-backup")]
    pub last_backup: Option<String>,
    #[serde(rename = "interface-addresses")]
    pub interface_addresses: HashMap<String, InterfaceAddress>,
    pub status: ServiceStatus,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CurrentDependencyInfo {
    #[serde(rename = "health-checks")]
    pub health_checks: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InterfaceAddress {
    #[serde(rename = "tor-address")]
    pub tor_address: String,
    #[serde(rename = "lan-address")]
    pub lan_address: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ServiceStatus {
    Stopped,
    Starting,
    Running,
    Stopping,
    Restarting,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct InstallProgress {
    pub size: u64,
    pub downloaded: u64,
}

/// WebSocket message sent to clients
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WebSocketMessage {
    pub rev: u32,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<DataModel>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub patch: Option<Vec<PatchOperation>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchOperation {
    pub op: String,
    pub path: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub from: Option<String>,
}

impl DataModel {
    /// Create a new empty data model with default values
    pub fn new() -> Self {
        Self {
            server_info: ServerInfo {
                id: uuid::Uuid::new_v4().to_string(),
                version: env!("CARGO_PKG_VERSION").to_string(),
                name: Some("Archipelago".to_string()),
                pubkey: String::new(),
                status_info: StatusInfo {
                    restarting: false,
                    shutting_down: false,
                    updated: false,
                    backup_progress: None,
                    update_progress: None,
                },
                lan_address: Some("http://localhost:8100".to_string()),
                tor_address: None,
                node_address: None,
                unread: 0,
                wifi_ssids: vec![],
                zram_enabled: false,
            },
            package_data: HashMap::new(),
            ui: UIData {
                name: None,
                ack_welcome: String::new(),
                marketplace: UIMarketplaceData {
                    selected_hosts: vec![],
                    known_hosts: HashMap::new(),
                },
                theme: "dark".to_string(),
            },
        }
    }
}

impl Default for DataModel {
    fn default() -> Self {
        Self::new()
    }
}
