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

use crate::AppState;

#[derive(Debug, Serialize)]
pub struct ErrorResponse {
  pub error: String,
}

pub async fn download(
  State(state): State<AppState>,
  Path((version, arch, filename)): Path<(String, String, String)>,
) -> Response {
  let release_dir = PathBuf::from(&state.config.release_dir);
  let file_path = release_dir.join(&version).join(&arch).join(&filename);

  if !file_path.exists() {
    return (
      StatusCode::NOT_FOUND,
      Json(ErrorResponse {
        error: format!("Release {} for {} not found: {}", version, arch, filename),
      }),
    )
      .into_response();
  }

  match std::fs::read(&file_path) {
    Ok(bytes) => {
      let headers = [
        ("Content-Type", "application/octet-stream".to_string()),
        (
          "Content-Disposition",
          format!("attachment; filename=\"{}\"", filename),
        ),
      ];
      (headers, bytes).into_response()
    }
    Err(e) => (
      StatusCode::INTERNAL_SERVER_ERROR,
      Json(ErrorResponse {
        error: format!("Failed to read release file: {}", e),
      }),
    )
      .into_response(),
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::config::Config;
  use ed25519_dalek::SigningKey;
  use sqlx::sqlite::SqlitePoolOptions;
  use tempfile::tempdir;

  async fn setup() -> (AppState, tempfile::TempDir) {
    let dir = tempdir().unwrap();
    let release_dir = dir.path().to_string_lossy().to_string();

    let pool = SqlitePoolOptions::new()
      .connect("sqlite::memory:")
      .await
      .unwrap();

    let config = Config {
      stripe_secret_key: "sk_test_placeholder".to_string(),
      stripe_price_id: String::new(),
      signing_key_hex: "42".repeat(64),
      database_url: "sqlite::memory:".to_string(),
      release_dir,
      static_dir: "./static".to_string(),
      port: 3000,
      app_url: "http://localhost:3000".to_string(),
    };

    let signing_key = SigningKey::from_bytes(&[42u8; 32]);

    let state = AppState {
      config,
      db: pool,
      signing_key,
      stripe_client: stripe::Client::new("sk_test_placeholder"),
    };

    (state, dir)
  }

  #[tokio::test]
  async fn test_download_serves_file() {
    let (state, dir) = setup().await;
    let release_dir = PathBuf::from(&state.config.release_dir);

    let version_dir = release_dir.join("0.1.0").join("x86_64");
    std::fs::create_dir_all(&version_dir).unwrap();
    std::fs::write(version_dir.join("clean-copy-0.1.0.exe"), b"fake binary").unwrap();

    let response = download(
      State(state),
      Path((
        "0.1.0".to_string(),
        "x86_64".to_string(),
        "clean-copy-0.1.0.exe".to_string(),
      )),
    )
    .await;

    assert_eq!(response.status(), StatusCode::OK);
    drop(dir);
  }

  #[tokio::test]
  async fn test_download_not_found() {
    let (state, _dir) = setup().await;

    let response = download(
      State(state),
      Path((
        "9.9.9".to_string(),
        "x86_64".to_string(),
        "clean-copy-9.9.9.exe".to_string(),
      )),
    )
    .await;

    assert_eq!(response.status(), StatusCode::NOT_FOUND);
  }

  #[tokio::test]
  async fn test_download_serves_msi() {
    let (state, dir) = setup().await;
    let release_dir = PathBuf::from(&state.config.release_dir);

    let version_dir = release_dir.join("0.1.0").join("x86_64");
    std::fs::create_dir_all(&version_dir).unwrap();
    std::fs::write(
      version_dir.join("CleanCopy_0.1.0_x64_en-US.msi"),
      b"fake msi",
    )
    .unwrap();

    let response = download(
      State(state),
      Path((
        "0.1.0".to_string(),
        "x86_64".to_string(),
        "CleanCopy_0.1.0_x64_en-US.msi".to_string(),
      )),
    )
    .await;

    assert_eq!(response.status(), StatusCode::OK);
    drop(dir);
  }
}
