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

/// Bulk license provisioning service for hosting providers (B2B Model B)
pub struct HostProvisioningService {
    db: PgPool,
}

impl HostProvisioningService {
    pub fn new(db: PgPool) -> Self {
        Self { db }
    }

    /// Verify host API key and return host_id
    pub async fn authenticate_host(&self, api_key: &str) -> Result<Uuid> {
        // Hash the provided API key
        let api_key_hash = sha256::digest(api_key);

        let host = sqlx::query!(
            "SELECT id FROM hosts WHERE api_key = $1 AND active = true",
            api_key_hash
        )
        .fetch_optional(&self.db)
        .await
        .context("Failed to verify host API key")?;

        host.map(|h| h.id)
            .ok_or_else(|| anyhow::anyhow!("Invalid or inactive host API key"))
    }

    /// Provision a new license for a hosting customer
    pub async fn provision_license(
        &self,
        host_id: Uuid,
        server_identifier: &str,
        customer_email: &str,
    ) -> Result<ProvisionedLicense> {
        // Generate license key (CORROSION-XXXXXXXX format)
        let license_key = format!(
            "CORROSION-{}",
            crate::services::encryption::generate_token(8).to_uppercase()
        );

        // Generate companion token (for bare metal agent authentication)
        let companion_token = crate::services::encryption::generate_token(32);

        // Create user account for customer (if doesn't exist)
        let user_id = self
            .get_or_create_user(customer_email, &license_key)
            .await?;

        // Create license
        let license_id = sqlx::query_scalar!(
            "INSERT INTO licenses (license_key, owner_user_id, server_name, subdomain)
             VALUES ($1, $2, $3, $4)
             RETURNING id",
            license_key,
            user_id,
            server_identifier,
            self.generate_subdomain(server_identifier),
        )
        .fetch_one(&self.db)
        .await
        .context("Failed to create license")?;

        // Associate license with host
        sqlx::query!(
            "INSERT INTO host_licenses (host_id, license_id, server_identifier, customer_email)
             VALUES ($1, $2, $3, $4)",
            host_id,
            license_id,
            server_identifier,
            customer_email
        )
        .execute(&self.db)
        .await
        .context("Failed to link license to host")?;

        // Store companion token
        sqlx::query!(
            "INSERT INTO server_connections (license_id, connection_type, companion_agent_token)
             VALUES ($1, 'bare_metal', $2)
             ON CONFLICT (license_id) DO UPDATE SET companion_agent_token = $2",
            license_id,
            companion_token
        )
        .execute(&self.db)
        .await
        .context("Failed to store companion token")?;

        tracing::info!(
            "Provisioned license {} for host {} (server: {}, customer: {})",
            license_key,
            host_id,
            server_identifier,
            customer_email
        );

        Ok(ProvisionedLicense {
            license_key,
            companion_token,
            plugin_download_url: format!(
                "https://cdn.corrosionmgmt.com/plugin/CorrosionCompanion-latest.cs"
            ),
            subdomain: self.generate_subdomain(server_identifier),
        })
    }

    /// Get existing user or create new one
    async fn get_or_create_user(&self, email: &str, license_key: &str) -> Result<Uuid> {
        // Check if user exists
        if let Some(user) = sqlx::query!("SELECT id FROM users WHERE email = $1", email)
            .fetch_optional(&self.db)
            .await?
        {
            return Ok(user.id);
        }

        // Create new user with random password (they'll reset via email)
        let random_password = crate::services::encryption::generate_token(16);
        let password_hash = crate::services::auth::hash_password(&random_password)?;

        // Extract username from email
        let username = email.split('@').next().unwrap_or("user");

        let user_id = crate::db::users::create_user(&self.db, email, username, &password_hash)
            .await
            .context("Failed to create user account")?;

        // TODO: Send welcome email with password reset link

        Ok(user_id)
    }

    /// Generate subdomain from server identifier
    fn generate_subdomain(&self, server_identifier: &str) -> String {
        // Sanitize: lowercase, replace non-alphanumeric with hyphens, trim hyphens
        server_identifier
            .to_lowercase()
            .chars()
            .map(|c| if c.is_alphanumeric() { c } else { '-' })
            .collect::<String>()
            .trim_matches('-')
            .to_string()
    }

    /// Get active license count for a host (for billing)
    pub async fn get_active_license_count(&self, host_id: Uuid) -> Result<i64> {
        let count = sqlx::query_scalar!(
            "SELECT COUNT(*) FROM host_licenses hl
             INNER JOIN licenses l ON l.id = hl.license_id
             WHERE hl.host_id = $1 AND l.status = 'active'",
            host_id
        )
        .fetch_one(&self.db)
        .await?
        .unwrap_or(0);

        Ok(count)
    }

    /// Generate monthly billing record for all hosts
    pub async fn generate_monthly_billing(&self, billing_month: chrono::NaiveDate) -> Result<Vec<HostBillingRecord>> {
        let hosts = sqlx::query!("SELECT id, company_name, wholesale_rate_usd FROM hosts WHERE active = true")
            .fetch_all(&self.db)
            .await?;

        let mut records = Vec::new();

        for host in hosts {
            let active_count = self.get_active_license_count(host.id).await?;

            if active_count == 0 {
                continue; // Skip hosts with no active licenses
            }

            let wholesale_rate = host.wholesale_rate_usd;
            let total_amount = wholesale_rate * rust_decimal::Decimal::from(active_count);

            // Insert billing record
            sqlx::query!(
                "INSERT INTO host_billing_records (host_id, billing_month, active_license_count, wholesale_rate_usd, total_amount_usd)
                 VALUES ($1, $2, $3, $4, $5)
                 ON CONFLICT (host_id, billing_month) DO UPDATE
                 SET active_license_count = $3, total_amount_usd = $5",
                host.id,
                billing_month,
                active_count as i32,
                wholesale_rate,
                total_amount
            )
            .execute(&self.db)
            .await?;

            records.push(HostBillingRecord {
                host_id: host.id,
                company_name: host.company_name,
                active_license_count: active_count as i32,
                wholesale_rate_usd: wholesale_rate,
                total_amount_usd: total_amount,
            });

            tracing::info!(
                "Generated billing record for host {} ({} servers @ ${}/mo = ${})",
                host.company_name,
                active_count,
                wholesale_rate,
                total_amount
            );
        }

        Ok(records)
    }
}

#[derive(Debug)]
pub struct ProvisionedLicense {
    pub license_key: String,
    pub companion_token: String,
    pub plugin_download_url: String,
    pub subdomain: String,
}

#[derive(Debug)]
pub struct HostBillingRecord {
    pub host_id: Uuid,
    pub company_name: String,
    pub active_license_count: i32,
    pub wholesale_rate_usd: rust_decimal::Decimal,
    pub total_amount_usd: rust_decimal::Decimal,
}
