use anyhow::Context;
use surrealdb::engine::remote::ws::{Client, Ws};
use surrealdb::opt::auth::Root;
use surrealdb::types::SurrealValue;
use surrealdb::Surreal;
use tracing::{info, warn};

use crate::error::{auth, ControlCenterError, Result};

/// Database service for SurrealDB operations
#[derive(Clone)]
pub struct DatabaseService {
    pub db: Surreal<Client>,
}

// Use the configuration from simple_config
use crate::simple_config::DatabaseConfig;

impl DatabaseService {
    /// Create a new database service and connect
    pub async fn new(config: DatabaseConfig) -> Result<Self> {
        info!("Connecting to SurrealDB at ws://{}", config.url);

        let db = Surreal::new::<Ws>(&*config.url)
            .await
            .context("Failed to connect to SurrealDB")?;

        // Sign in only when credentials are explicitly configured
        if let (Some(username), Some(password)) = (&config.username, &config.password) {
            db.signin(Root {
                username: username.clone(),
                password: password.clone(),
            })
                .await
                .context("Failed to sign in to SurrealDB")?;
        }

        // Use the specified namespace and database
        db.use_ns(&config.namespace)
            .use_db(&config.database)
            .await
            .context("Failed to select namespace and database")?;

        info!(
            "Successfully connected to SurrealDB namespace: {}, database: {}",
            config.namespace, config.database
        );

        let service = Self { db };

        // Initialize database schema
        service.initialize_schema().await?;

        Ok(service)
    }

    /// Initialize database schema and system data
    async fn initialize_schema(&self) -> Result<()> {
        info!("Initializing database schema");

        // Create users table with indexes
        self.db
            .query(
                "
                DEFINE TABLE users SCHEMAFULL;
                DEFINE FIELD user_id ON TABLE users TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD email ON TABLE users TYPE string ASSERT $value != NONE AND \
                 string::len($value) > 3 AND string::contains($value, '@');
                DEFINE FIELD username ON TABLE users TYPE string ASSERT $value != NONE;
                DEFINE FIELD password_hash ON TABLE users TYPE string ASSERT $value != NONE;
                DEFINE FIELD first_name ON TABLE users TYPE option<string>;
                DEFINE FIELD last_name ON TABLE users TYPE option<string>;
                DEFINE FIELD is_active ON TABLE users TYPE bool DEFAULT true;
                DEFINE FIELD is_verified ON TABLE users TYPE bool DEFAULT false;
                DEFINE FIELD roles ON TABLE users TYPE array<string> DEFAULT ['user'];
                DEFINE FIELD created_at ON TABLE users TYPE datetime DEFAULT time::now();
                DEFINE FIELD updated_at ON TABLE users TYPE datetime DEFAULT time::now();
                DEFINE FIELD last_login ON TABLE users TYPE option<datetime>;

                DEFINE INDEX users_email_idx ON TABLE users COLUMNS email UNIQUE;
                DEFINE INDEX users_username_idx ON TABLE users COLUMNS username UNIQUE;
                DEFINE INDEX users_user_id_idx ON TABLE users COLUMNS user_id UNIQUE;
                ",
            )
            .await
            .context("Failed to create users table")?;

        // Create roles table with indexes
        self.db
            .query(
                "
                DEFINE TABLE roles SCHEMAFULL;
                DEFINE FIELD role_id ON TABLE roles TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD name ON TABLE roles TYPE string ASSERT $value != NONE;
                DEFINE FIELD description ON TABLE roles TYPE option<string>;
                DEFINE FIELD permissions ON TABLE roles TYPE array<string> DEFAULT [];
                DEFINE FIELD is_system ON TABLE roles TYPE bool DEFAULT false;
                DEFINE FIELD created_at ON TABLE roles TYPE datetime DEFAULT time::now();
                DEFINE FIELD updated_at ON TABLE roles TYPE datetime DEFAULT time::now();

                DEFINE INDEX roles_name_idx ON TABLE roles COLUMNS name UNIQUE;
                DEFINE INDEX roles_role_id_idx ON TABLE roles COLUMNS role_id UNIQUE;
                ",
            )
            .await
            .context("Failed to create roles table")?;

        // Create permissions table
        self.db
            .query(
                "
                DEFINE TABLE permissions SCHEMAFULL;
                DEFINE FIELD permission_id ON TABLE permissions TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD name ON TABLE permissions TYPE string ASSERT $value != NONE;
                DEFINE FIELD resource ON TABLE permissions TYPE string ASSERT $value != NONE;
                DEFINE FIELD action ON TABLE permissions TYPE string ASSERT $value != NONE;
                DEFINE FIELD description ON TABLE permissions TYPE option<string>;
                DEFINE FIELD created_at ON TABLE permissions TYPE datetime DEFAULT time::now();

                DEFINE INDEX permissions_name_idx ON TABLE permissions COLUMNS name UNIQUE;
                DEFINE INDEX permissions_permission_id_idx ON TABLE permissions COLUMNS \
                 permission_id UNIQUE;
                ",
            )
            .await
            .context("Failed to create permissions table")?;

        // Create sessions table with indexes
        self.db
            .query(
                "
                DEFINE TABLE sessions SCHEMAFULL;
                DEFINE FIELD session_id ON TABLE sessions TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD user_id ON TABLE sessions TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD refresh_token ON TABLE sessions TYPE string ASSERT $value != NONE;
                DEFINE FIELD is_active ON TABLE sessions TYPE bool DEFAULT true;
                DEFINE FIELD created_at ON TABLE sessions TYPE datetime DEFAULT time::now();
                DEFINE FIELD expires_at ON TABLE sessions TYPE datetime ASSERT $value != NONE;
                DEFINE FIELD last_used ON TABLE sessions TYPE datetime DEFAULT time::now();
                DEFINE FIELD client_info ON TABLE sessions TYPE option<object>;

                DEFINE INDEX sessions_session_id_idx ON TABLE sessions COLUMNS session_id UNIQUE;
                DEFINE INDEX sessions_user_id_idx ON TABLE sessions COLUMNS user_id;
                DEFINE INDEX sessions_refresh_token_idx ON TABLE sessions COLUMNS refresh_token \
                 UNIQUE;
                ",
            )
            .await
            .context("Failed to create sessions table")?;

        // Create detection_results table for IaC detection
        self.db
            .query(
                "
                DEFINE TABLE detection_results SCHEMAFULL;
                DEFINE FIELD id ON TABLE detection_results TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD project_path ON TABLE detection_results TYPE string ASSERT $value != \
                 NONE;
                DEFINE FIELD organization ON TABLE detection_results TYPE string ASSERT $value != \
                 NONE;
                DEFINE FIELD technologies ON TABLE detection_results TYPE array DEFAULT [];
                DEFINE FIELD completeness ON TABLE detection_results TYPE float DEFAULT 0.0;
                DEFINE FIELD confidence ON TABLE detection_results TYPE float DEFAULT 0.0;
                DEFINE FIELD metadata ON TABLE detection_results TYPE object DEFAULT {};
                DEFINE FIELD created_at ON TABLE detection_results TYPE datetime DEFAULT \
                 time::now();
                DEFINE FIELD updated_at ON TABLE detection_results TYPE datetime DEFAULT \
                 time::now();

                DEFINE INDEX detection_results_id_idx ON TABLE detection_results COLUMNS id UNIQUE;
                DEFINE INDEX detection_results_org_idx ON TABLE detection_results COLUMNS \
                 organization;
                DEFINE INDEX detection_results_project_idx ON TABLE detection_results COLUMNS \
                 project_path;
                ",
            )
            .await
            .context("Failed to create detection_results table")?;

        // Create inference_rules table for rule management
        self.db
            .query(
                "
                DEFINE TABLE inference_rules SCHEMAFULL;
                DEFINE FIELD id ON TABLE inference_rules TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD name ON TABLE inference_rules TYPE string ASSERT $value != NONE;
                DEFINE FIELD organization ON TABLE inference_rules TYPE string ASSERT $value != \
                 NONE;
                DEFINE FIELD triggers ON TABLE inference_rules TYPE array DEFAULT [];
                DEFINE FIELD inferences ON TABLE inference_rules TYPE array DEFAULT [];
                DEFINE FIELD enabled ON TABLE inference_rules TYPE bool DEFAULT true;
                DEFINE FIELD priority ON TABLE inference_rules TYPE int DEFAULT 10;
                DEFINE FIELD created_at ON TABLE inference_rules TYPE datetime DEFAULT time::now();
                DEFINE FIELD updated_at ON TABLE inference_rules TYPE datetime DEFAULT time::now();

                DEFINE INDEX inference_rules_id_idx ON TABLE inference_rules COLUMNS id UNIQUE;
                DEFINE INDEX inference_rules_org_idx ON TABLE inference_rules COLUMNS organization;
                DEFINE INDEX inference_rules_enabled_idx ON TABLE inference_rules COLUMNS enabled;
                DEFINE INDEX inference_rules_priority_idx ON TABLE inference_rules COLUMNS \
                 priority;
                ",
            )
            .await
            .context("Failed to create inference_rules table")?;

        // Create deployment_plans table for deployment management
        self.db
            .query(
                "
                DEFINE TABLE deployment_plans SCHEMAFULL;
                DEFINE FIELD id ON TABLE deployment_plans TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD name ON TABLE deployment_plans TYPE string ASSERT $value != NONE;
                DEFINE FIELD description ON TABLE deployment_plans TYPE string DEFAULT '';
                DEFINE FIELD organization ON TABLE deployment_plans TYPE string ASSERT $value != \
                 NONE;
                DEFINE FIELD tasks ON TABLE deployment_plans TYPE array DEFAULT [];
                DEFINE FIELD status ON TABLE deployment_plans TYPE string DEFAULT 'pending';
                DEFINE FIELD created_at ON TABLE deployment_plans TYPE datetime DEFAULT \
                 time::now();
                DEFINE FIELD updated_at ON TABLE deployment_plans TYPE datetime DEFAULT \
                 time::now();
                DEFINE FIELD scheduled_at ON TABLE deployment_plans TYPE option<datetime>;

                DEFINE INDEX deployment_plans_id_idx ON TABLE deployment_plans COLUMNS id UNIQUE;
                DEFINE INDEX deployment_plans_org_idx ON TABLE deployment_plans COLUMNS \
                 organization;
                DEFINE INDEX deployment_plans_status_idx ON TABLE deployment_plans COLUMNS status;
                ",
            )
            .await
            .context("Failed to create deployment_plans table")?;

        // Create deployment_executions table for tracking execution status
        self.db
            .query(
                "
                DEFINE TABLE deployment_executions SCHEMAFULL;
                DEFINE FIELD id ON TABLE deployment_executions TYPE uuid ASSERT $value != NONE;
                DEFINE FIELD plan_id ON TABLE deployment_executions TYPE uuid ASSERT $value != \
                 NONE;
                DEFINE FIELD workflow_id ON TABLE deployment_executions TYPE option<string>;
                DEFINE FIELD status ON TABLE deployment_executions TYPE string DEFAULT 'pending';
                DEFINE FIELD progress_percentage ON TABLE deployment_executions TYPE int DEFAULT 0;
                DEFINE FIELD completed_tasks ON TABLE deployment_executions TYPE int DEFAULT 0;
                DEFINE FIELD total_tasks ON TABLE deployment_executions TYPE int DEFAULT 0;
                DEFINE FIELD started_at ON TABLE deployment_executions TYPE option<datetime>;
                DEFINE FIELD completed_at ON TABLE deployment_executions TYPE option<datetime>;
                DEFINE FIELD created_at ON TABLE deployment_executions TYPE datetime DEFAULT \
                 time::now();

                DEFINE INDEX deployment_executions_id_idx ON TABLE deployment_executions COLUMNS \
                 id UNIQUE;
                DEFINE INDEX deployment_executions_plan_idx ON TABLE deployment_executions COLUMNS \
                 plan_id;
                DEFINE INDEX deployment_executions_status_idx ON TABLE deployment_executions \
                 COLUMNS status;
                ",
            )
            .await
            .context("Failed to create deployment_executions table")?;

        info!("Database schema initialized successfully");

        // Initialize system roles and permissions
        self.initialize_system_data().await?;

        Ok(())
    }

    /// Initialize system roles and permissions
    async fn initialize_system_data(&self) -> Result<()> {
        use crate::models::{Permission, Role};

        info!("Initializing system roles and permissions");

        // Create system permissions if they don't exist
        let permissions = Permission::create_system_permissions();
        for permission in permissions {
            let permission_name = permission.name.clone();
            let existing: Option<crate::models::Permission> = self
                .db
                .select(("permissions", permission_name.clone()))
                .await
                .context("Failed to check existing permission")?;

            if existing.is_none() {
                // Use raw query with proper UUID cast syntax
                let query = format!(
                    "CREATE permissions CONTENT {{
                        permission_id: <uuid>'{}',
                        name: '{}',
                        resource: '{}',
                        action: '{}',
                        description: {}
                    }}",
                    permission.permission_id,
                    permission.name.replace("'", "\\'"),
                    permission.resource.replace("'", "\\'"),
                    permission.action.replace("'", "\\'"),
                    permission
                        .description
                        .as_ref()
                        .map(|d| format!("'{}'", d.replace("'", "\\'")))
                        .unwrap_or_else(|| "NONE".to_string())
                );

                self.db
                    .query(query)
                    .await
                    .context("Failed to create system permission")?;
            }
        }

        // Create system roles if they don't exist
        let roles = Role::create_system_roles();
        for role in roles {
            let role_name = role.name.clone();
            let existing: Option<crate::models::Role> = self
                .db
                .select(("roles", role_name.clone()))
                .await
                .context("Failed to check existing role")?;

            if existing.is_none() {
                // Use raw query with proper UUID cast syntax
                let query = format!(
                    "CREATE roles CONTENT {{
                        role_id: <uuid>'{}',
                        name: '{}',
                        description: {},
                        permissions: {},
                        is_system: {}
                    }}",
                    role.role_id,
                    role.name.replace("'", "\\'"),
                    role.description
                        .as_ref()
                        .map(|d| format!("'{}'", d.replace("'", "\\'")))
                        .unwrap_or_else(|| "NONE".to_string()),
                    serde_json::to_string(&role.permissions).unwrap_or_else(|_| "[]".to_string()),
                    role.is_system
                );

                self.db
                    .query(query)
                    .await
                    .context("Failed to create system role")?;
            }
        }

        info!("System roles and permissions initialized");
        Ok(())
    }

    /// Health check for database connection
    pub async fn health_check(&self) -> Result<bool> {
        match self.db.query("SELECT * FROM $auth LIMIT 1").await {
            Ok(_) => Ok(true),
            Err(e) => {
                warn!("Database health check failed: {}", e);
                Err(ControlCenterError::from(
                    crate::error::database::DatabaseError::Database(format!(
                        "Health check failed: {}",
                        e
                    )),
                ))
            }
        }
    }

    /// Clean up expired sessions
    pub async fn cleanup_expired_sessions(&self) -> Result<usize> {
        let mut result = self
            .db
            .query("DELETE sessions WHERE expires_at < time::now() OR is_active = false")
            .await
            .context("Failed to cleanup expired sessions")?;

        // Get the count of deleted records
        let count: Vec<usize> = result.take(0).unwrap_or_default();
        let deleted_count = count.into_iter().next().unwrap_or(0);

        if deleted_count > 0 {
            info!("Cleaned up {} expired sessions", deleted_count);
        }

        Ok(deleted_count)
    }

    /// Get database statistics
    pub async fn get_statistics(&self) -> Result<DatabaseStatistics> {
        let mut result = self
            .db
            .query(
                "
                SELECT
                    (SELECT count() FROM users)[0] as users_count,
                    (SELECT count() FROM roles)[0] as roles_count,
                    (SELECT count() FROM permissions)[0] as permissions_count,
                    (SELECT count() FROM sessions WHERE is_active = true)[0] as \
                 active_sessions_count,
                    (SELECT count() FROM sessions)[0] as total_sessions_count
                ",
            )
            .await
            .context("Failed to get database statistics")?;

        let stats: Vec<DatabaseStatistics> = result
            .take(0)
            .context("Failed to parse database statistics")?;

        stats.into_iter().next().ok_or_else(|| {
            ControlCenterError::from(crate::error::database::DatabaseError::Database(
                "No statistics returned".to_string(),
            ))
        })
    }
}

/// Database statistics
#[derive(Debug, serde::Deserialize, serde::Serialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct DatabaseStatistics {
    pub users_count: i64,
    pub roles_count: i64,
    pub permissions_count: i64,
    pub active_sessions_count: i64,
    pub total_sessions_count: i64,
}

#[cfg(test)]
mod tests {
    use tempfile::tempdir;

    use super::*;

    #[tokio::test]
    async fn test_database_connection() {
        // This test requires a running SurrealDB instance
        // Skip if not available in CI/testing environment
        if std::env::var("SKIP_DB_TESTS").is_ok() {
            return;
        }

        let config = DatabaseConfig::default();
        let result = DatabaseService::new(config).await;

        match result {
            Ok(db_service) => {
                assert!(db_service.health_check().await.unwrap_or(false));
            }
            Err(e) => {
                // If we can't connect, that's expected in a test environment
                println!("Database connection test skipped: {}", e);
            }
        }
    }
}
