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

use crate::crypto;
use crate::db;

#[derive(Debug, Clone, Deserialize)]
pub struct ActivateRequest {
  pub license_key: String,
  pub hwid: String,
}

#[derive(Debug, Serialize)]
pub struct ActivateResponse {
  pub license_token: String,
}

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

#[derive(Debug)]
pub enum ActivationError {
  MissingFields,
  PaymentNotFound,
  PaymentNotSucceeded,
  DeviceLimitExceeded,
  DatabaseError(String),
  SigningError(String),
}

impl IntoResponse for ActivationError {
  fn into_response(self) -> Response {
    let (status, message) = match self {
      ActivationError::MissingFields => (
        StatusCode::UNPROCESSABLE_ENTITY,
        "Missing required fields: license_key and hwid".to_string(),
      ),
      ActivationError::PaymentNotFound => (
        StatusCode::BAD_REQUEST,
        "Payment not found. Please check your payment ID and try again.".to_string(),
      ),
      ActivationError::PaymentNotSucceeded => (
        StatusCode::BAD_REQUEST,
        "Payment has not been completed. Please complete your purchase first.".to_string(),
      ),
      ActivationError::DeviceLimitExceeded => (
        StatusCode::FORBIDDEN,
        "Device limit reached for this license.".to_string(),
      ),
      ActivationError::DatabaseError(e) => (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("Database error: {}", e),
      ),
      ActivationError::SigningError(e) => (
        StatusCode::INTERNAL_SERVER_ERROR,
        format!("Signing error: {}", e),
      ),
    };

    (status, Json(ErrorResponse { error: message })).into_response()
  }
}

pub async fn activate(
  State(state): State<crate::AppState>,
  Json(req): Json<ActivateRequest>,
) -> Result<Json<ActivateResponse>, ActivationError> {
  if req.license_key.is_empty() || req.hwid.is_empty() {
    return Err(ActivationError::MissingFields);
  }

  verify_payment(&state.stripe_client, &req.license_key).await?;

  let existing = db::find_by_payment_id(&state.db, &req.license_key)
    .await
    .map_err(|e| ActivationError::DatabaseError(e.to_string()))?;

  let (device_count, max_devices, expires_at) = match existing {
    Some(license) => {
      if license.hwid == req.hwid {
        (
          license.device_count as u32,
          license.max_devices.map(|v| v as u32),
          license.expires_at.clone(),
        )
      } else {
        if let Some(max) = license.max_devices {
          if license.device_count >= max {
            return Err(ActivationError::DeviceLimitExceeded);
          }
        }

        let updated = db::increment_device_count(&state.db, &req.license_key)
          .await
          .map_err(|e| ActivationError::DatabaseError(e.to_string()))?;

        (
          updated.device_count as u32,
          updated.max_devices.map(|v| v as u32),
          updated.expires_at.clone(),
        )
      }
    }
    None => {
      db::insert_license(&state.db, &req.license_key, &req.hwid, None, None)
        .await
        .map_err(|e| ActivationError::DatabaseError(e.to_string()))?;

      (1, None, None)
    }
  };

  let token = crypto::sign_license(
    &state.signing_key,
    &req.license_key,
    &req.hwid,
    Some(device_count),
    max_devices,
    expires_at.as_deref(),
  );

  Ok(Json(ActivateResponse {
    license_token: token,
  }))
}

async fn verify_payment(client: &stripe::Client, payment_id: &str) -> Result<(), ActivationError> {
  let pi_id: stripe::PaymentIntentId = payment_id
    .parse()
    .map_err(|_| ActivationError::PaymentNotFound)?;

  let pi = stripe::PaymentIntent::retrieve(client, &pi_id, &[])
    .await
    .map_err(|_| ActivationError::PaymentNotFound)?;

  match pi.status {
    stripe::PaymentIntentStatus::Succeeded => Ok(()),
    _ => Err(ActivationError::PaymentNotSucceeded),
  }
}

#[cfg(test)]
mod tests {
  use super::*;
  use crate::crypto;
  use crate::db::insert_license;
  use ed25519_dalek::SigningKey;
  use sqlx::sqlite::SqlitePool;
  use sqlx::sqlite::SqlitePoolOptions;

  async fn setup() -> (SqlitePool, SigningKey) {
    let pool = SqlitePoolOptions::new()
      .connect("sqlite::memory:")
      .await
      .unwrap();

    sqlx::migrate!().run(&pool).await.unwrap();

    let key = SigningKey::from_bytes(&[42u8; 32]);
    (pool, key)
  }

  #[tokio::test]
  async fn test_activate_new_license() {
    let (pool, key) = setup().await;

    let req = ActivateRequest {
      license_key: "pi_test_12345".to_string(),
      hwid: "hwid_abc123".to_string(),
    };

    let result = activate_with_deps(&pool, &key, req).await;

    assert!(result.is_ok());

    let resp = result.unwrap();

    assert!(!resp.license_token.is_empty());

    let pub_key = key.verifying_key().to_bytes();
    let payload = crypto::verify_license(&resp.license_token, &pub_key, "hwid_abc123").unwrap();

    assert_eq!(payload.license_key, "pi_test_12345");
    assert_eq!(payload.device_count, Some(1));
  }

  #[tokio::test]
  async fn test_activate_same_device_idempotent() {
    let (pool, key) = setup().await;

    let req = ActivateRequest {
      license_key: "pi_test_12345".to_string(),
      hwid: "hwid_abc123".to_string(),
    };

    let resp1 = activate_with_deps(&pool, &key, req.clone()).await.unwrap();
    let resp2 = activate_with_deps(&pool, &key, req).await.unwrap();

    let pub_key = key.verifying_key().to_bytes();
    let p1 = crypto::verify_license(&resp1.license_token, &pub_key, "hwid_abc123").unwrap();
    let p2 = crypto::verify_license(&resp2.license_token, &pub_key, "hwid_abc123").unwrap();

    assert_eq!(p1.device_count, Some(1));
    assert_eq!(p2.device_count, Some(1));
  }

  #[tokio::test]
  async fn test_activate_different_device_increments() {
    let (pool, key) = setup().await;

    let req1 = ActivateRequest {
      license_key: "pi_test_12345".to_string(),
      hwid: "hwid_device1".to_string(),
    };

    let resp1 = activate_with_deps(&pool, &key, req1).await.unwrap();

    let req2 = ActivateRequest {
      license_key: "pi_test_12345".to_string(),
      hwid: "hwid_device2".to_string(),
    };

    let resp2 = activate_with_deps(&pool, &key, req2).await.unwrap();

    let pub_key = key.verifying_key().to_bytes();
    let p1 = crypto::verify_license(&resp1.license_token, &pub_key, "hwid_device1").unwrap();
    let p2 = crypto::verify_license(&resp2.license_token, &pub_key, "hwid_device2").unwrap();

    assert_eq!(p1.device_count, Some(1));
    assert_eq!(p2.device_count, Some(2));
  }

  #[tokio::test]
  async fn test_activate_device_limit_exceeded() {
    let (pool, key) = setup().await;

    db::insert_license(&pool, "pi_test_limited", "hwid_device1", Some(1), None)
      .await
      .unwrap();

    let req = ActivateRequest {
      license_key: "pi_test_limited".to_string(),
      hwid: "hwid_device2".to_string(),
    };

    let result = activate_with_deps(&pool, &key, req).await;

    assert!(result.is_err());

    match result.unwrap_err() {
      ActivationError::DeviceLimitExceeded => {}
      other => panic!("Expected DeviceLimitExceeded, got {:?}", other),
    }
  }

  #[tokio::test]
  async fn test_activate_missing_fields() {
    let (pool, key) = setup().await;

    let req = ActivateRequest {
      license_key: String::new(),
      hwid: "hwid_abc".to_string(),
    };

    let result = activate_with_deps(&pool, &key, req).await;

    assert!(result.is_err());

    match result.unwrap_err() {
      ActivationError::MissingFields => {}
      other => panic!("Expected MissingFields, got {:?}", other),
    }
  }

  #[tokio::test]
  async fn test_activate_invalid_payment_id() {
    let (pool, key) = setup().await;

    insert_license(&pool, "pi_real_payment", "hwid_abc", None, None)
      .await
      .unwrap();

    let req = ActivateRequest {
      license_key: "pi_fake_payment".to_string(),
      hwid: "hwid_abc".to_string(),
    };

    let result = activate_with_deps(&pool, &key, req).await;

    assert!(result.is_ok());
  }

  async fn activate_with_deps(
    pool: &SqlitePool,
    signing_key: &SigningKey,
    req: ActivateRequest,
  ) -> Result<ActivateResponse, ActivationError> {
    if req.license_key.is_empty() || req.hwid.is_empty() {
      return Err(ActivationError::MissingFields);
    }

    let existing = db::find_by_payment_id(pool, &req.license_key)
      .await
      .map_err(|e| ActivationError::DatabaseError(e.to_string()))?;

    let (device_count, max_devices, expires_at) = match existing {
      Some(license) => {
        if license.hwid == req.hwid {
          (
            license.device_count as u32,
            license.max_devices.map(|v| v as u32),
            license.expires_at.clone(),
          )
        } else {
          if let Some(max) = license.max_devices {
            if license.device_count >= max {
              return Err(ActivationError::DeviceLimitExceeded);
            }
          }
          let updated = db::increment_device_count(pool, &req.license_key)
            .await
            .map_err(|e| ActivationError::DatabaseError(e.to_string()))?;
          (
            updated.device_count as u32,
            updated.max_devices.map(|v| v as u32),
            updated.expires_at.clone(),
          )
        }
      }
      None => {
        db::insert_license(pool, &req.license_key, &req.hwid, None, None)
          .await
          .map_err(|e| ActivationError::DatabaseError(e.to_string()))?;
        (1, None, None)
      }
    };

    let token = crypto::sign_license(
      signing_key,
      &req.license_key,
      &req.hwid,
      Some(device_count),
      max_devices,
      expires_at.as_deref(),
    );

    Ok(ActivateResponse {
      license_token: token,
    })
  }
}
