use serde::{Deserialize, Serialize};

// ---------------------------------------------------------------------------
// Package type
// ---------------------------------------------------------------------------

pub const SUPPORTED_TARGET_PLATFORMS: [&str; 3] =
    ["x86_64-windows", "x86_64-linux", "aarch64-linux"];

pub fn is_supported_target_platform(value: &str) -> bool {
    SUPPORTED_TARGET_PLATFORMS.contains(&value)
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum PackageType {
    Plugin,
    Subsystem,
    Nodos,
    Engine,
    Generic,
}

impl PackageType {
    pub fn as_str(&self) -> &'static str {
        match self {
            PackageType::Plugin => "Plugin",
            PackageType::Subsystem => "Subsystem",
            PackageType::Nodos => "Nodos",
            PackageType::Engine => "Engine",
            PackageType::Generic => "Generic",
        }
    }

    pub fn from_str(s: &str) -> Self {
        match s {
            "Plugin" => PackageType::Plugin,
            "Subsystem" => PackageType::Subsystem,
            "Nodos" => PackageType::Nodos,
            "Engine" => PackageType::Engine,
            _ => PackageType::Generic,
        }
    }

    /// True for types that carry a plugin API version (Plugin and Subsystem).
    pub fn has_api_version(&self) -> bool {
        matches!(self, PackageType::Plugin | PackageType::Subsystem)
    }
}

impl Default for PackageType {
    fn default() -> Self {
        PackageType::Generic
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum PackageVisibility {
    #[default]
    Public,
    Private,
}

impl PackageVisibility {
    pub fn as_str(&self) -> &'static str {
        match self {
            PackageVisibility::Public => "public",
            PackageVisibility::Private => "private",
        }
    }

    pub fn from_str(value: &str) -> Self {
        match value {
            "private" => PackageVisibility::Private,
            _ => PackageVisibility::Public,
        }
    }
}

impl std::fmt::Display for PackageType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

// ---------------------------------------------------------------------------
// API version
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct ApiVersion {
    pub major: u32,
    #[serde(default)]
    pub minor: Option<u32>,
    #[serde(default)]
    pub patch: Option<u32>,
}

impl ApiVersion {
    pub fn new(major: u32, minor: Option<u32>, patch: Option<u32>) -> Self {
        Self {
            major,
            minor,
            patch,
        }
    }
}

// ---------------------------------------------------------------------------
// Package & release responses
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, Deserialize)]
pub struct Package {
    pub name: String,
    pub package_type: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PackageListResponse {
    pub packages: Vec<Package>,
    pub has_next_page: bool,
}

#[derive(Clone, Debug, Deserialize)]
pub(crate) struct PackageDetailResponse {
    pub package: Package,
}

#[derive(Clone, Debug, Deserialize)]
pub struct Artifact {
    pub id: i64,
    pub target_platform: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct Release {
    pub id: i64,
    pub version: String,
    #[serde(default)]
    pub tags: Vec<String>,
    pub artifacts: Vec<Artifact>,
    #[serde(default)]
    pub api_version: Option<ApiVersion>,
    #[serde(default)]
    pub dependencies: Vec<PackageDependency>,
    pub updated_at: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PackageDependency {
    pub name: String,
    pub version: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct ReleaseListResponse {
    pub releases: Vec<Release>,
    pub has_next_page: bool,
}

// ---------------------------------------------------------------------------
// Authentication
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, Deserialize)]
pub struct DeviceStartResponse {
    pub device_code: String,
    pub user_code: String,
    pub verification_uri: String,
    pub verification_uri_complete: String,
    pub expires_at: String,
    pub interval_seconds: u64,
}

#[derive(Clone, Debug, Deserialize)]
pub struct DevicePollResponse {
    pub access_token: Option<String>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct AccountInfo {
    pub needs_onboarding: bool,
}

// ---------------------------------------------------------------------------
// Publishing
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PublishSessionCreateRequest {
    pub name: String,
    pub display_name: String,
    pub description: String,
    #[serde(default)]
    pub package_type: PackageType,
    pub category: String,
    pub version: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub api_version: Option<ApiVersion>,
    #[serde(default)]
    pub dependencies: Vec<PackageDependency>,
    #[serde(default)]
    pub tags: Vec<String>,
    #[serde(default)]
    pub artifacts: Vec<PublishDraftArtifact>,
    /// Per-package visibility, fixed at first publish. Use
    /// `PackageVisibility::default()` for `Public`. Updating visibility for
    /// an existing package requires the dashboard
    /// `PATCH /v1/account/packages/{name}/visibility` endpoint, not this
    /// field.
    pub visibility: PackageVisibility,
    /// Optional publisher-authored changelog for the release. When omitted,
    /// the Nodos Store derives one from the artifact diff at finalize time.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub changelog: Option<String>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PublishDraftArtifact {
    pub target_platform: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PublishSession {
    pub session: PublishSessionSummary,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PublishSessionSummary {
    pub id: i64,
}

#[derive(Clone, Debug, Serialize)]
pub struct PublishUploadUrlRequest {
    pub target_platform: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PublishUploadUrl {
    pub upload_url: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PublishFinalizeResponse {
    pub release: Release,
}

// ---------------------------------------------------------------------------
// Downloads
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, Deserialize)]
pub struct DownloadTokenResponse {
    pub download_url: String,
}

// ---------------------------------------------------------------------------
// Generic responses
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, Deserialize)]
pub struct MessageResponse {
    pub message: String,
}

// ---------------------------------------------------------------------------
// Token storage
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StoredAccessToken {
    pub access_token: String,
}

// ---------------------------------------------------------------------------
// Error response
// ---------------------------------------------------------------------------

#[derive(Clone, Debug, Deserialize)]
pub(crate) struct ApiErrorResponse {
    pub message: String,
}
