use crate::{ServerJSONResp, Str};
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string};
use std::{collections::HashMap, env::consts::ARCH};

pub mod install;
mod other_fields;

pub use install::*;
pub use other_fields::*;

#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[doc = "Use the official ahqstore (https://crates.io/crates/ahqstore_cli_rs) cli\n🎯 Introduced in v1, Revamped in v2"]
pub struct AHQStoreApplication {
  /// The ID of the application
  pub appId: Str,

  /// The name of the shortcut of the app
  pub appShortcutName: Str,

  /// The name that'll be displayed in the app
  pub appDisplayName: Str,

  /// Unique ID of the author
  pub authorId: Str,

  /// The TagName of the release, MUST NOT BE LATEST
  pub releaseTagName: Str,

  /// Download URLs that the app will address
  pub downloadUrls: HashMap<u8, DownloadUrl>,

  /// Install options
  pub install: InstallerOptions,

  /// App display images referencing /resources, let the cli do it
  pub displayImages: Vec<u8>,

  /// App description
  pub description: Str,

  /// Your Github Repo associated
  pub repo: AppRepo,

  /// Will be generated by the cli, it is the unique-version-hash of your app
  pub version: Str,

  /// The Site to your app
  pub site: Option<Str>,

  /// This'll be ignored unless you're ahq_verified tag which no one except AHQ Store Team has
  ///
  /// The general dev isn't meant to redistribute others' apps unless they own right to do so
  pub source: Option<Str>,

  /// License type or Terms of Service Page
  pub license_or_tos: Option<Str>,

  /// Page of Application
  pub app_page: Option<Str>,

  /// These Resources will be passed to the installer, the size of all the Vec<u8> must not be more than 5 * 1024 * 1024 * 1024 bytes (~5MB)
  pub resources: Option<HashMap<u8, Vec<u8>>>,
}

impl AHQStoreApplication {
  #[cfg(feature = "apps_repo")]
  pub fn validate(&self) -> Result<String, String> {
    let mut result = String::new();

    result.push_str(&self.validate_resource());

    if &self.authorId != "1" {
      if &self.releaseTagName == "latest" {
        result.push_str("❌ ReleaseTagName can't be latest\n");
      }

      if let Some(_) = self.source {
        result.push_str("❌ Source can't be present, your application must be original\n");
      }
    }

    if result.contains("❌") {
      Err(result)
    } else {
      Ok(result)
    }
  }

  #[cfg(feature = "apps_repo")]
  pub fn validate_resource(&self) -> String {
    let Some(x) = &self.resources else {
      return "❌ No Resources Present".into();
    };

    let mut total = 0;

    x.iter().for_each(|(_, v)| total += v.len());

    let mut resp = String::new();

    if total > 5 * 1024 * 1024 * 1024 {
      resp.push_str("❌ Total size of all resources combined must not be more than 5MB\n");
    } else if self.displayImages.len() <= 5 {
      resp.push_str("❌ A maximum of 5 images can be set in displayImages\n");
    } else if x.get(&0).is_none() {
      resp.push_str("❌ Resource with id 0 must be present as it represents icon\n");
    } else if !x.iter().all(|(id, _)| x.get(&(*id + 1)).is_some()) {
      resp.push_str("❌ Every display images should have their resource id\n");
    } else {
      resp.push_str("✅ Resources are valid\n");
    }

    resp
  }

  pub fn export(&self) -> (String, Vec<(u8, Vec<u8>)>) {
    let mut obj = self.clone();

    for val in obj.downloadUrls.values_mut() {
      if &obj.authorId == "1" && &val.asset == "url" {
        continue;
      }

      let path = format!(
        "https://github.com/{}/{}/releases/download/{}/{}",
        self.repo.author, self.repo.repo, self.releaseTagName, val.asset
      );

      val.url = path;
    }
    let resources: Vec<(u8, Vec<u8>)> = std::mem::replace(&mut obj.resources, None)
      .map_or(vec![], |map| map.into_iter().collect::<Vec<_>>());

    (to_string(&obj).unwrap(), resources)
  }

  pub fn list_os_arch(&self) -> Vec<&'static str> {
    self.install.list_os_arch()
  }

  pub fn is_supported(&self) -> bool {
    self.install.is_supported()
  }

  pub fn has_platform(&self) -> bool {
    self.install.has_platform()
  }

  #[doc = "🎯 Introduced in v2"]
  pub fn get_win_download(&self) -> Option<&DownloadUrl> {
    let get_w32 = || {
      let Some(x) = &self.install.win32 else {
        return None;
      };

      Some(x)
    };

    // If we are on aarch64, we prefer to use native arm build
    let win32 = if ARCH == "aarch64" {
      if let Some(arm) = &self.install.winarm {
        arm
      } else {
        get_w32()?
      }
    } else {
      get_w32()?
    };

    let url = self.downloadUrls.get(&win32.assetId)?;

    match &url.installerType {
      InstallerFormat::WindowsZip
      | InstallerFormat::WindowsInstallerExe
      | InstallerFormat::WindowsInstallerMsi
      | InstallerFormat::WindowsUWPMsix => Some(&url),
      _ => None,
    }
  }

  #[doc = "🎯 Introduced in v2"]
  /// Just a clone of get_win_download for backwards compatibility
  pub fn get_win32_download(&self) -> Option<&DownloadUrl> {
    self.get_win_download()
  }

  #[doc = "🎯 Introduced in v2"]
  pub fn get_win_extension(&self) -> Option<&'static str> {
    match self.get_win_download()?.installerType {
      InstallerFormat::WindowsZip => Some(".zip"),
      InstallerFormat::WindowsInstallerExe => Some(".exe"),
      InstallerFormat::WindowsInstallerMsi => Some(".msi"),
      InstallerFormat::WindowsUWPMsix => Some(".msix"),
      _ => None,
    }
  }

  #[doc = "🎯 Introduced in v2"]
  /// Just a clone of get_win_extention for backwards compatibility
  pub fn get_win32_extension(&self) -> Option<&'static str> {
    self.get_win_extension()
  }

  #[doc = "🎯 Introduced in v2"]
  pub fn get_linux_download(&self) -> Option<&DownloadUrl> {
    let linux = match ARCH {
      "x86_64" => self.install.linux.as_ref()?,
      "aarch64" => self.install.linuxArm64.as_ref()?,
      "arm" => self.install.linuxArm7.as_ref()?,
      _ => {
        return None;
      }
    };

    let url = self.downloadUrls.get(&linux.assetId)?;

    match &url.installerType {
      InstallerFormat::LinuxAppImage => Some(&url),
      _ => None,
    }
  }

  #[doc = "🎯 Introduced in v2"]
  pub fn get_linux_extension(&self) -> Option<&'static str> {
    match self.get_linux_download()?.installerType {
      InstallerFormat::LinuxAppImage => Some(".AppImage"),
      _ => None,
    }
  }

  #[doc = "🎯 Introduced in v2"]
  pub fn get_android_download(&self) -> Option<&DownloadUrl> {
    let Some(android) = &self.install.android else {
      return None;
    };

    let url = self.downloadUrls.get(&android.assetId)?;

    match &url.installerType {
      InstallerFormat::AndroidApkZip => Some(&url),
      _ => None,
    }
  }

  #[doc = "🎯 Introduced in v2"]
  pub fn get_android_extension(&self) -> Option<&'static str> {
    match self.get_android_download()?.installerType {
      InstallerFormat::AndroidApkZip => Some(".apk"),
      _ => None,
    }
  }
}

impl TryFrom<ServerJSONResp> for AHQStoreApplication {
  type Error = serde_json::Error;

  fn try_from(value: ServerJSONResp) -> Result<Self, Self::Error> {
    from_str(&value.config)
  }
}
