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

use crate::models::license::{License, LicenseCheckInResponse};
use crate::services::encryption;

/// License validation and lifecycle management.
///
/// Handles license key validation, activation (binding a key to a server),
/// periodic check-ins from the Rust plugin, and license lookups.
/// All license state is stored in PostgreSQL.
pub struct LicenseService {
    db: sqlx::PgPool,
}

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

    /// Validate a license key and return its current status.
    ///
    /// Checks: key exists, not expired, not revoked, modules enabled.
    pub async fn validate_license(&self, license_key: &str) -> Result<Option<License>> {
        let license: Option<License> = sqlx::query_as(
            "SELECT * FROM licenses WHERE license_key = $1 AND status = 'active'",
        )
        .bind(license_key)
        .fetch_optional(&self.db)
        .await
        .context("Failed to query license")?;

        if let Some(ref lic) = license {
            // Check expiration
            if let Some(expires_at) = lic.expires_at {
                if expires_at < chrono::Utc::now() {
                    return Ok(None);
                }
            }
        }

        Ok(license)
    }

    /// Activate a license key: bind it to a server name and subdomain.
    ///
    /// Called during initial setup when a user first registers.
    pub async fn activate_license(
        &self,
        license_key: &str,
        server_name: &str,
        subdomain: &str,
    ) -> Result<License> {
        // Check license exists and is pending
        let existing: Option<License> = sqlx::query_as(
            "SELECT * FROM licenses WHERE license_key = $1 AND status = 'pending'",
        )
        .bind(license_key)
        .fetch_optional(&self.db)
        .await
        .context("Failed to query license for activation")?;

        if existing.is_none() {
            anyhow::bail!("License key not found or already activated");
        }

        // Check subdomain availability
        let subdomain_exists: Option<(bool,)> = sqlx::query_as(
            "SELECT EXISTS(SELECT 1 FROM licenses WHERE subdomain = $1) as exists",
        )
        .bind(subdomain)
        .fetch_optional(&self.db)
        .await
        .context("Failed to check subdomain availability")?;

        if subdomain_exists.map_or(false, |(exists,)| exists) {
            anyhow::bail!("Subdomain already in use");
        }

        // Activate license
        let license: License = sqlx::query_as(
            "UPDATE licenses
             SET status = 'active', server_name = $2, subdomain = $3, updated_at = NOW()
             WHERE license_key = $1
             RETURNING *",
        )
        .bind(license_key)
        .bind(server_name)
        .bind(subdomain)
        .fetch_one(&self.db)
        .await
        .context("Failed to activate license")?;

        // Create default system roles for this license
        self.create_default_roles(license.id).await?;

        tracing::info!(
            "License activated: {} (subdomain: {}, server: {})",
            license_key,
            subdomain,
            server_name
        );

        Ok(license)
    }

    /// Create default system roles for a newly activated license.
    async fn create_default_roles(&self, license_id: Uuid) -> Result<()> {
        // Owner role (full permissions)
        sqlx::query(
            "INSERT INTO roles (license_id, role_name, is_system_default, permissions)
             VALUES ($1, 'Owner', true, $2)",
        )
        .bind(license_id)
        .bind(serde_json::json!({
            "wipes": ["read", "write", "execute"],
            "maps": ["read", "write", "delete"],
            "plugins": ["read", "write", "configure"],
            "schedules": ["read", "write", "delete"],
            "team": ["read", "write", "delete"],
            "settings": ["read", "write"],
            "logs": ["read"]
        }))
        .execute(&self.db)
        .await
        .context("Failed to create Owner role")?;

        // Admin role (most permissions, no team deletion)
        sqlx::query(
            "INSERT INTO roles (license_id, role_name, is_system_default, permissions)
             VALUES ($1, 'Admin', true, $2)",
        )
        .bind(license_id)
        .bind(serde_json::json!({
            "wipes": ["read", "write", "execute"],
            "maps": ["read", "write"],
            "plugins": ["read", "write", "configure"],
            "schedules": ["read", "write"],
            "team": ["read", "write"],
            "settings": ["read"],
            "logs": ["read"]
        }))
        .execute(&self.db)
        .await
        .context("Failed to create Admin role")?;

        // Viewer role (read-only)
        sqlx::query(
            "INSERT INTO roles (license_id, role_name, is_system_default, permissions)
             VALUES ($1, 'Viewer', true, $2)",
        )
        .bind(license_id)
        .bind(serde_json::json!({
            "wipes": ["read"],
            "maps": ["read"],
            "plugins": ["read"],
            "schedules": ["read"],
            "team": ["read"],
            "settings": ["read"],
            "logs": ["read"]
        }))
        .execute(&self.db)
        .await
        .context("Failed to create Viewer role")?;

        Ok(())
    }

    /// Process a check-in from the Rust server plugin.
    ///
    /// Updates last-seen timestamp and returns current license state
    /// including enabled modules and NATS connection token.
    pub async fn check_in(
        &self,
        license_key: &str,
        plugin_version: &str,
    ) -> Result<LicenseCheckInResponse> {
        // Validate license
        let license = self.validate_license(license_key).await?;

        if license.is_none() {
            return Ok(LicenseCheckInResponse {
                valid: false,
                status: "invalid".to_string(),
                modules_enabled: vec![],
                nats_token: String::new(),
            });
        }

        let license = license.unwrap();

        // Update last-seen timestamp in server_connections
        sqlx::query(
            "INSERT INTO server_connections (license_id, plugin_version, plugin_last_seen)
             VALUES ($1, $2, NOW())
             ON CONFLICT (license_id)
             DO UPDATE SET plugin_version = $2, plugin_last_seen = NOW()",
        )
        .bind(license.id)
        .bind(plugin_version)
        .execute(&self.db)
        .await
        .context("Failed to update plugin check-in")?;

        // Generate NATS auth token (simple token based on license ID)
        let nats_token = encryption::generate_token(32);

        // Store token in DB for validation later
        sqlx::query(
            "UPDATE licenses SET nats_token = $2, updated_at = NOW() WHERE id = $1",
        )
        .bind(license.id)
        .bind(&nats_token)
        .execute(&self.db)
        .await
        .context("Failed to store NATS token")?;

        tracing::debug!(
            "License check-in: {} (version: {})",
            license_key,
            plugin_version
        );

        Ok(LicenseCheckInResponse {
            valid: true,
            status: license.status.clone(),
            modules_enabled: license.modules_enabled.unwrap_or_default(),
            nats_token,
        })
    }

    /// Look up a license by its UUID.
    pub async fn get_license_by_id(&self, license_id: Uuid) -> Result<Option<License>> {
        let license: Option<License> = sqlx::query_as("SELECT * FROM licenses WHERE id = $1")
            .bind(license_id)
            .fetch_optional(&self.db)
            .await
            .context("Failed to query license by ID")?;

        Ok(license)
    }

    /// Look up a license by its key.
    pub async fn get_license_by_key(&self, license_key: &str) -> Result<Option<License>> {
        let license: Option<License> = sqlx::query_as("SELECT * FROM licenses WHERE license_key = $1")
            .bind(license_key)
            .fetch_optional(&self.db)
            .await
            .context("Failed to query license by key")?;

        Ok(license)
    }
}
