use chrono::Utc;
use sqlx::sqlite::{SqlitePool, SqlitePoolOptions};

#[derive(Debug, Clone, sqlx::FromRow)]
pub struct License {
  pub id: i64,
  pub payment_id: String,
  pub hwid: String,
  pub device_count: i64,
  pub max_devices: Option<i64>,
  pub expires_at: Option<String>,
  pub created_at: String,
  pub updated_at: String,
}

pub async fn create_pool(database_url: &str) -> Result<SqlitePool, sqlx::Error> {
  if let Some(path) = database_url
    .strip_prefix("sqlite:")
    .and_then(|s| s.split('?').next())
  {
    if let Some(parent) = std::path::Path::new(path).parent() {
      std::fs::create_dir_all(parent).ok();
    }
  }

  let pool = SqlitePoolOptions::new().connect(database_url).await?;

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

  Ok(pool)
}

pub async fn insert_license(
  pool: &SqlitePool,
  payment_id: &str,
  hwid: &str,
  max_devices: Option<i64>,
  expires_at: Option<&str>,
) -> Result<License, sqlx::Error> {
  let now = Utc::now().to_rfc3339();

  let result = sqlx::query_as::<_, License>(
    "INSERT INTO licenses (payment_id, hwid, device_count, max_devices, expires_at, created_at, updated_at)
     VALUES (?, ?, 1, ?, ?, ?, ?)
     RETURNING id, payment_id, hwid, device_count, max_devices, expires_at, created_at, updated_at"
  )
    .bind(payment_id)
    .bind(hwid)
    .bind(max_devices)
    .bind(expires_at)
    .bind(&now)
    .bind(&now)
    .fetch_one(pool)
    .await?;

  Ok(result)
}

pub async fn find_by_payment_id(
  pool: &SqlitePool,
  payment_id: &str,
) -> Result<Option<License>, sqlx::Error> {
  let result = sqlx::query_as::<_, License>(
    "SELECT id, payment_id, hwid, device_count, max_devices, expires_at, created_at, updated_at
         FROM licenses WHERE payment_id = ?",
  )
  .bind(payment_id)
  .fetch_optional(pool)
  .await?;

  Ok(result)
}

pub async fn increment_device_count(
  pool: &SqlitePool,
  payment_id: &str,
) -> Result<License, sqlx::Error> {
  let now = Utc::now().to_rfc3339();

  sqlx::query_as::<_, License>(
        "UPDATE licenses SET device_count = device_count + 1, updated_at = ?
         WHERE payment_id = ? RETURNING id, payment_id, hwid, device_count, max_devices, expires_at, created_at, updated_at"
    )
    .bind(&now)
    .bind(payment_id)
    .fetch_one(pool)
    .await
}

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

  async fn setup_db() -> SqlitePool {
    create_pool("sqlite::memory:").await.unwrap()
  }

  #[sqlx::test]
  async fn test_insert_new_license() {
    let pool = setup_db().await;

    let license = insert_license(&pool, "pi_test_123", "hwid_abc", None, None)
      .await
      .unwrap();

    assert_eq!(license.payment_id, "pi_test_123");
    assert_eq!(license.hwid, "hwid_abc");
    assert_eq!(license.device_count, 1);
    assert!(license.max_devices.is_none());
    assert!(license.expires_at.is_none());
  }

  #[sqlx::test]
  async fn test_insert_with_limits() {
    let pool = setup_db().await;

    let license = insert_license(
      &pool,
      "pi_test_456",
      "hwid_def",
      Some(3),
      Some("2025-12-31"),
    )
    .await
    .unwrap();

    assert_eq!(license.max_devices, Some(3));
    assert_eq!(license.expires_at.as_deref(), Some("2025-12-31"));
  }

  #[sqlx::test]
  async fn test_insert_duplicate_payment_id_same_hwid() {
    let pool = setup_db().await;

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

    let result = insert_license(&pool, "pi_test_123", "hwid_abc", None, None).await;

    assert!(result.is_err());
  }

  #[sqlx::test]
  async fn test_find_by_payment_id_found() {
    let pool = setup_db().await;

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

    let found = find_by_payment_id(&pool, "pi_test_123").await.unwrap();

    assert!(found.is_some());
    assert_eq!(found.unwrap().hwid, "hwid_abc");
  }

  #[sqlx::test]
  async fn test_find_by_payment_id_not_found() {
    let pool = setup_db().await;

    let found = find_by_payment_id(&pool, "pi_nonexistent").await.unwrap();

    assert!(found.is_none());
  }

  #[sqlx::test]
  async fn test_increment_device_count() {
    let pool = setup_db().await;

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

    let updated = increment_device_count(&pool, "pi_test_123").await.unwrap();

    assert_eq!(updated.device_count, 2);

    let updated = increment_device_count(&pool, "pi_test_123").await.unwrap();

    assert_eq!(updated.device_count, 3);
  }
}
