use sqlx::PgPool;
use uuid::Uuid;
use anyhow::{Context, Result};

use crate::models::modules::{Module, ModuleInstallation, ModuleWithOwnership, PurchasedModule};

/// Get all available modules from the marketplace catalog.
pub async fn get_module_catalog(pool: &PgPool) -> Result<Vec<Module>> {
    let modules = sqlx::query_as::<_, Module>(
        "SELECT * FROM modules ORDER BY category, name"
    )
    .fetch_all(pool)
    .await
    .context("Failed to fetch module catalog")?;

    Ok(modules)
}

/// Get modules with ownership flag for a specific license.
/// Returns all modules annotated with is_purchased for the given license.
pub async fn get_catalog_with_ownership(pool: &PgPool, license_id: Uuid) -> Result<Vec<ModuleWithOwnership>> {
    let rows: Vec<(Module, Option<Uuid>, Option<String>)> = sqlx::query_as(
        "SELECT
            m.*,
            mp.id as purchase_id,
            mi.status as installation_status
         FROM modules m
         LEFT JOIN module_purchases mp ON mp.module_id = m.id AND mp.license_id = $1
         LEFT JOIN module_installations mi ON mi.module_id = m.id AND mi.license_id = $1
         ORDER BY m.category, m.name"
    )
    .bind(license_id)
    .fetch_all(pool)
    .await
    .context("Failed to fetch catalog with ownership")?;

    let modules = rows.into_iter().map(|(module, purchase_id, installation_status)| {
        ModuleWithOwnership {
            module,
            is_purchased: purchase_id.is_some(),
            installation_status,
        }
    }).collect();

    Ok(modules)
}

/// Get all modules purchased by a specific license.
pub async fn get_purchased_modules(pool: &PgPool, license_id: Uuid) -> Result<Vec<PurchasedModule>> {
    type Row = (
        // Module fields
        Uuid, String, String, Option<String>, Option<String>, rust_decimal::Decimal,
        Option<String>, Option<serde_json::Value>, Option<serde_json::Value>, String,
        Option<String>, chrono::DateTime<chrono::Utc>,
        // Purchase fields
        chrono::DateTime<chrono::Utc>,
        // Installation fields
        Option<String>, Option<chrono::DateTime<chrono::Utc>>, Option<String>
    );

    let rows: Vec<Row> = sqlx::query_as(
        "SELECT
            m.id, m.slug, m.name, m.description, m.category, m.price_usd,
            m.preview_image_url, m.screenshots, m.features, m.version,
            m.plugin_file_url, m.created_at,
            mp.purchased_at,
            mi.status as installation_status,
            mi.installed_at,
            mi.error_message
         FROM modules m
         INNER JOIN module_purchases mp ON mp.module_id = m.id
         LEFT JOIN module_installations mi ON mi.module_id = m.id AND mi.license_id = mp.license_id
         WHERE mp.license_id = $1
         ORDER BY mp.purchased_at DESC"
    )
    .bind(license_id)
    .fetch_all(pool)
    .await
    .context("Failed to fetch purchased modules")?;

    let modules = rows.into_iter().map(|row| {
        PurchasedModule {
            module: Module {
                id: row.0,
                slug: row.1,
                name: row.2,
                description: row.3,
                category: row.4,
                price_usd: row.5,
                preview_image_url: row.6,
                screenshots: row.7,
                features: row.8,
                version: row.9,
                plugin_file_url: row.10,
                created_at: row.11,
            },
            purchased_at: row.12,
            installation_status: row.13,
            installed_at: row.14,
            error_message: row.15,
        }
    }).collect();

    Ok(modules)
}

/// Check if a module is purchased by a license.
pub async fn is_module_purchased(pool: &PgPool, license_id: Uuid, module_id: Uuid) -> Result<bool> {
    let exists: (bool,) = sqlx::query_as(
        "SELECT EXISTS(SELECT 1 FROM module_purchases WHERE license_id = $1 AND module_id = $2)"
    )
    .bind(license_id)
    .bind(module_id)
    .fetch_one(pool)
    .await
    .context("Failed to check module ownership")?;

    Ok(exists.0)
}

/// Record a module purchase.
pub async fn record_module_purchase(
    pool: &PgPool,
    license_id: Uuid,
    module_id: Uuid,
    transaction_id: Option<&str>,
    amount: Option<rust_decimal::Decimal>,
) -> Result<Uuid> {
    let purchase_id: Uuid = sqlx::query_scalar(
        "INSERT INTO module_purchases (license_id, module_id, transaction_id, amount_paid)
         VALUES ($1, $2, $3, $4)
         ON CONFLICT (license_id, module_id) DO UPDATE
         SET transaction_id = EXCLUDED.transaction_id, amount_paid = EXCLUDED.amount_paid
         RETURNING id"
    )
    .bind(license_id)
    .bind(module_id)
    .bind(transaction_id)
    .bind(amount)
    .fetch_one(pool)
    .await
    .context("Failed to record module purchase")?;

    Ok(purchase_id)
}

/// Get installation status for a module.
pub async fn get_module_installation_status(
    pool: &PgPool,
    license_id: Uuid,
    module_id: Uuid,
) -> Result<Option<ModuleInstallation>> {
    let installation = sqlx::query_as::<_, ModuleInstallation>(
        "SELECT * FROM module_installations WHERE license_id = $1 AND module_id = $2"
    )
    .bind(license_id)
    .bind(module_id)
    .fetch_optional(pool)
    .await
    .context("Failed to fetch installation status")?;

    Ok(installation)
}

/// Update module installation status.
pub async fn update_installation_status(
    pool: &PgPool,
    license_id: Uuid,
    module_id: Uuid,
    status: &str,
    error: Option<&str>,
) -> Result<()> {
    let now = if status == "installed" {
        Some(chrono::Utc::now())
    } else {
        None
    };

    sqlx::query(
        "INSERT INTO module_installations (license_id, module_id, status, installed_at, error_message)
         VALUES ($1, $2, $3, $4, $5)
         ON CONFLICT (license_id, module_id)
         DO UPDATE SET
            status = EXCLUDED.status,
            installed_at = EXCLUDED.installed_at,
            error_message = EXCLUDED.error_message"
    )
    .bind(license_id)
    .bind(module_id)
    .bind(status)
    .bind(now)
    .bind(error)
    .execute(pool)
    .await
    .context("Failed to update installation status")?;

    Ok(())
}

/// Get a module by ID.
pub async fn get_module_by_id(pool: &PgPool, module_id: Uuid) -> Result<Option<Module>> {
    let module = sqlx::query_as::<_, Module>(
        "SELECT * FROM modules WHERE id = $1"
    )
    .bind(module_id)
    .fetch_optional(pool)
    .await
    .context("Failed to fetch module by ID")?;

    Ok(module)
}

/// Get a module by slug.
pub async fn get_module_by_slug(pool: &PgPool, slug: &str) -> Result<Option<Module>> {
    let module = sqlx::query_as::<_, Module>(
        "SELECT * FROM modules WHERE slug = $1"
    )
    .bind(slug)
    .fetch_optional(pool)
    .await
    .context("Failed to fetch module by slug")?;

    Ok(module)
}
