use axum::extract::{Path, State};
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

use crate::AppState;

#[derive(Debug, Serialize)]
pub struct UpdateManifest {
  pub version: String,
  pub notes: String,
  pub pub_date: String,
  pub platforms: UpdatePlatforms,
}

#[derive(Debug, Serialize)]
pub struct UpdatePlatforms {
  #[serde(rename = "windows-x86_64")]
  pub windows_x86_64: Option<PlatformEntry>,
}

#[derive(Debug, Serialize)]
pub struct PlatformEntry {
  pub signature: String,
  pub url: String,
}

#[derive(Debug, Deserialize)]
pub struct VersionParts {
  pub major: u32,
  pub minor: u32,
  pub patch: u32,
}

impl VersionParts {
  pub fn parse(version: &str) -> Option<Self> {
    let parts: Vec<&str> = version.trim_start_matches('v').split('.').collect();
    if parts.len() != 3 {
      return None;
    }
    Some(Self {
      major: parts[0].parse().ok()?,
      minor: parts[1].parse().ok()?,
      patch: parts[2].parse().ok()?,
    })
  }

  pub fn is_newer_than(&self, other: &Self) -> bool {
    (self.major, self.minor, self.patch) > (other.major, other.minor, other.patch)
  }

  pub fn to_string(&self) -> String {
    format!("{}.{}.{}", self.major, self.minor, self.patch)
  }
}

pub async fn updater(
  State(state): State<AppState>,
  Path((_target, arch, current_version)): Path<(String, String, String)>,
) -> Response {
  let current = match VersionParts::parse(&current_version) {
    Some(v) => v,
    None => {
      return (
        StatusCode::BAD_REQUEST,
        Json(serde_json::json!({ "error": "Invalid version format" })),
      )
        .into_response()
    }
  };

  let release_dir = PathBuf::from(&state.config.release_dir);

  if !release_dir.exists() {
    return StatusCode::NO_CONTENT.into_response();
  }

  let mut latest: Option<VersionParts> = None;

  let entries = match std::fs::read_dir(&release_dir) {
    Ok(e) => e,
    Err(_) => return StatusCode::NO_CONTENT.into_response(),
  };

  for entry in entries.flatten() {
    if !entry.file_type().map(|t| t.is_dir()).unwrap_or(false) {
      continue;
    }

    let dir_name = entry.file_name().to_string_lossy().to_string();
    if let Some(ver) = VersionParts::parse(&dir_name) {
      if ver.is_newer_than(&current) {
        if latest.as_ref().map_or(true, |l| ver.is_newer_than(l)) {
          latest = Some(ver);
        }
      }
    }
  }

  let latest = match latest {
    Some(v) => v,
    None => return StatusCode::NO_CONTENT.into_response(),
  };

  let platform_dir = release_dir.join(latest.to_string()).join(&arch);
  if !platform_dir.exists() {
    return StatusCode::NO_CONTENT.into_response();
  }

  // Find the release file
  let file_entry = std::fs::read_dir(&platform_dir).ok().and_then(|entries| {
    entries
      .flatten()
      .find(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
  });

  let file_entry = match file_entry {
    Some(e) => e,
    None => return StatusCode::NO_CONTENT.into_response(),
  };

  let file_name = file_entry.file_name().to_string_lossy().to_string();
  let download_url = format!(
    "{}/releases/download/{}/{}/{}",
    state.config.app_url,
    latest.to_string(),
    arch,
    file_name
  );

  let manifest = UpdateManifest {
    version: latest.to_string(),
    notes: format!("Update to version {}", latest.to_string()),
    pub_date: chrono::Utc::now().to_rfc3339(),
    platforms: UpdatePlatforms {
      windows_x86_64: Some(PlatformEntry {
        signature: String::new(), // TODO: Generate or store signature
        url: download_url,
      }),
    },
  };

  (StatusCode::OK, Json(manifest)).into_response()
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn test_version_parse_valid() {
    let v = VersionParts::parse("0.2.0").unwrap();
    assert_eq!(v.major, 0);
    assert_eq!(v.minor, 2);
    assert_eq!(v.patch, 0);
  }

  #[test]
  fn test_version_parse_with_v_prefix() {
    let v = VersionParts::parse("v1.0.0").unwrap();
    assert_eq!(v.major, 1);
    assert_eq!(v.minor, 0);
    assert_eq!(v.patch, 0);
  }

  #[test]
  fn test_version_parse_invalid() {
    assert!(VersionParts::parse("invalid").is_none());
    assert!(VersionParts::parse("1.0").is_none());
    assert!(VersionParts::parse("1.0.0.0").is_none());
  }

  #[test]
  fn test_version_is_newer_than() {
    let v1 = VersionParts::parse("0.1.0").unwrap();
    let v2 = VersionParts::parse("0.2.0").unwrap();
    let v3 = VersionParts::parse("1.0.0").unwrap();

    assert!(v2.is_newer_than(&v1));
    assert!(v3.is_newer_than(&v2));
    assert!(!v1.is_newer_than(&v2));
    assert!(!v1.is_newer_than(&v1));
  }

  #[test]
  fn test_version_to_string() {
    let v = VersionParts::parse("1.2.3").unwrap();
    assert_eq!(v.to_string(), "1.2.3");
  }
}
