use std::path::PathBuf;

use askama::Template;
use askama_web::WebTemplate;
use axum::extract::State;

use crate::AppState;

#[derive(Template, WebTemplate)]
#[template(path = "success.html")]
pub struct SuccessTemplate {
  pub email: &'static str,
  pub has_download: bool,
  pub has_msi: bool,
  pub download_version: String,
  pub exe_url: String,
  pub msi_url: String,
}

pub async fn success_page(State(state): State<AppState>) -> SuccessTemplate {
  let latest = find_latest_release(&state.config.release_dir);
  SuccessTemplate {
    email: crate::constants::CONTACT_EMAIL,
    has_download: latest.is_some(),
    has_msi: latest
      .as_ref()
      .map(|r| !r.msi_url.is_empty())
      .unwrap_or(false),
    download_version: latest
      .as_ref()
      .map(|r| r.version.clone())
      .unwrap_or_default(),
    exe_url: latest
      .as_ref()
      .map(|r| r.exe_url.clone())
      .unwrap_or_default(),
    msi_url: latest
      .as_ref()
      .map(|r| r.msi_url.clone())
      .unwrap_or_default(),
  }
}

struct VersionParts {
  major: u32,
  minor: u32,
  patch: u32,
}

impl VersionParts {
  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()?,
    })
  }

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

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

struct LatestRelease {
  version: String,
  exe_url: String,
  msi_url: String,
}

fn find_latest_release(release_dir: &str) -> Option<LatestRelease> {
  let dir = PathBuf::from(release_dir);
  if !dir.exists() {
    return None;
  }

  let entries = std::fs::read_dir(&dir).ok()?;
  let mut latest: Option<VersionParts> = None;

  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 latest.as_ref().map_or(true, |l| ver.is_newer_than(l)) {
        latest = Some(ver);
      }
    }
  }

  let latest = latest?;
  let arch_dir = dir.join(latest.to_string()).join("x86_64");
  if !arch_dir.exists() {
    return None;
  }

  let exe_file = find_file_with_ext(&arch_dir, "exe")?;
  let msi_file = find_file_with_ext(&arch_dir, "msi");

  let version = latest.to_string();
  let exe_url = format!(
    "/releases/download/{}/x86_64/{}",
    version,
    exe_file.file_name().to_string_lossy()
  );
  let msi_url = msi_file
    .map(|f| {
      format!(
        "/releases/download/{}/x86_64/{}",
        version,
        f.file_name().to_string_lossy()
      )
    })
    .unwrap_or_default();

  Some(LatestRelease {
    version,
    exe_url,
    msi_url,
  })
}

fn find_file_with_ext(dir: &PathBuf, ext: &str) -> Option<std::fs::DirEntry> {
  std::fs::read_dir(dir)
    .ok()?
    .filter_map(|e| e.ok())
    .find(|e| {
      e.file_type().map(|t| t.is_file()).unwrap_or(false)
        && e.path()
          .extension()
          .map(|e| e.eq_ignore_ascii_case(ext))
          .unwrap_or(false)
    })
}

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

  #[test]
  fn test_find_latest_release_empty_dir() {
    let dir = tempdir().unwrap();
    assert!(find_latest_release(&dir.path().to_string_lossy()).is_none());
  }

  #[test]
  fn test_find_latest_release_no_arch_dir() {
    let dir = tempdir().unwrap();
    std::fs::create_dir_all(dir.path().join("0.1.0").join("aarch64")).unwrap();
    assert!(find_latest_release(&dir.path().to_string_lossy()).is_none());
  }

  #[test]
  fn test_find_latest_release_empty_arch_dir() {
    let dir = tempdir().unwrap();
    std::fs::create_dir_all(dir.path().join("0.1.0").join("x86_64")).unwrap();
    assert!(find_latest_release(&dir.path().to_string_lossy()).is_none());
  }

  #[test]
  fn test_find_latest_release_exe_only() {
    let dir = tempdir().unwrap();
    let arch_dir = dir.path().join("0.1.0").join("x86_64");
    std::fs::create_dir_all(&arch_dir).unwrap();
    std::fs::write(arch_dir.join("clean-copy-0.1.0.exe"), b"fake").unwrap();

    let result = find_latest_release(&dir.path().to_string_lossy()).unwrap();
    assert_eq!(result.version, "0.1.0");
    assert!(result.exe_url.contains("clean-copy-0.1.0.exe"));
    assert!(result.msi_url.is_empty());
  }

  #[test]
  fn test_find_latest_release_exe_and_msi() {
    let dir = tempdir().unwrap();
    let arch_dir = dir.path().join("0.2.0").join("x86_64");
    std::fs::create_dir_all(&arch_dir).unwrap();
    std::fs::write(arch_dir.join("clean-copy-0.2.0.exe"), b"fake exe").unwrap();
    std::fs::write(
      arch_dir.join("CleanCopy_0.2.0_x64_en-US.msi"),
      b"fake msi",
    )
    .unwrap();

    let result = find_latest_release(&dir.path().to_string_lossy()).unwrap();
    assert_eq!(result.version, "0.2.0");
    assert!(result.exe_url.contains("clean-copy-0.2.0.exe"));
    assert!(result
      .msi_url
      .contains("CleanCopy_0.2.0_x64_en-US.msi"));
  }

  #[test]
  fn test_find_latest_release_picks_highest() {
    let dir = tempdir().unwrap();

    for version in &["0.1.0", "0.3.0", "0.2.0"] {
      let arch_dir = dir.path().join(version).join("x86_64");
      std::fs::create_dir_all(&arch_dir).unwrap();
      std::fs::write(arch_dir.join("app.exe"), b"fake").unwrap();
    }

    let result = find_latest_release(&dir.path().to_string_lossy()).unwrap();
    assert_eq!(result.version, "0.3.0");
  }

  #[test]
  fn test_find_latest_release_skips_invalid_dirs() {
    let dir = tempdir().unwrap();

    let valid = dir.path().join("0.2.0").join("x86_64");
    std::fs::create_dir_all(&valid).unwrap();
    std::fs::write(valid.join("app.exe"), b"fake").unwrap();

    std::fs::create_dir_all(dir.path().join("not-a-version").join("x86_64")).unwrap();
    std::fs::create_dir_all(dir.path().join("0.1").join("x86_64")).unwrap();

    let result = find_latest_release(&dir.path().to_string_lossy()).unwrap();
    assert_eq!(result.version, "0.2.0");
  }

  #[test]
  fn test_find_latest_release_nonexistent_dir() {
    assert!(find_latest_release("/nonexistent/path").is_none());
  }

  #[test]
  fn test_find_latest_release_msi_only_no_exe() {
    let dir = tempdir().unwrap();
    let arch_dir = dir.path().join("0.1.0").join("x86_64");
    std::fs::create_dir_all(&arch_dir).unwrap();
    std::fs::write(
      arch_dir.join("CleanCopy_0.1.0_x64_en-US.msi"),
      b"fake msi",
    )
    .unwrap();

    assert!(find_latest_release(&dir.path().to_string_lossy()).is_none());
  }
}
