use anyhow::Result;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::path::PathBuf;
use std::time::Duration;
use tokio::fs;
use tokio::time::sleep;

#[derive(Debug)]
pub struct DirectorySetup {
    base_url: String,
    client: Client,
    admin_token: Option<String>,
    config_path: PathBuf,
}

impl DirectorySetup {
    pub fn set_admin_token(&mut self, token: String) {
        self.admin_token = Some(token);
    }

    pub fn ensure_admin_token(&mut self) -> Result<()> {
        if self.admin_token.is_none() {
            return Err(anyhow::anyhow!("Admin token must be configured"));
        }
        Ok(())
    }

    fn generate_secure_password() -> String {
        use rand::distr::Alphanumeric;
        use rand::Rng;
        let mut rng = rand::rng();
        (0..16)
            .map(|_| {
                let byte = rng.sample(Alphanumeric);
                char::from(byte)
            })
            .collect()
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DefaultOrganization {
    pub id: String,
    pub name: String,
    pub domain: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DefaultUser {
    pub id: String,
    pub username: String,
    pub email: String,
    pub password: String,
    pub first_name: String,
    pub last_name: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct DirectoryConfig {
    pub base_url: String,
    pub default_org: DefaultOrganization,
    pub default_user: DefaultUser,
    pub admin_token: String,
    pub project_id: String,
    pub client_id: String,
    pub client_secret: String,
}

impl DirectorySetup {
    pub fn new(base_url: String, config_path: PathBuf) -> Self {
        Self {
            base_url,
            client: Client::builder()
                .timeout(Duration::from_secs(30))
                .build()
                .unwrap_or_else(|e| {
                    log::warn!("Failed to create HTTP client with timeout: {}, using default", e);
                    Client::new()
                }),
            admin_token: None,
            config_path,
        }
    }

    pub async fn wait_for_ready(&self, max_attempts: u32) -> Result<()> {
        log::info!("Waiting for Directory service to be ready...");

        for attempt in 1..=max_attempts {
            match self
                .client
                .get(format!("{}/debug/ready", self.base_url))
                .send()
                .await
            {
                Ok(response) if response.status().is_success() => {
                    log::info!("Directory service is ready!");
                    return Ok(());
                }
                _ => {
                    log::debug!(
                        "Directory not ready yet (attempt {}/{})",
                        attempt,
                        max_attempts
                    );
                    sleep(Duration::from_secs(3)).await;
                }
            }
        }

        anyhow::bail!("Directory service did not become ready in time")
    }

    pub async fn initialize(&mut self) -> Result<DirectoryConfig> {
        log::info!(" Initializing Directory (Zitadel) with defaults...");

        if let Ok(existing_config) = self.load_existing_config().await {
            log::info!("Directory already initialized, using existing config");
            return Ok(existing_config);
        }

        self.wait_for_ready(30).await?;

        self.ensure_admin_token()?;

        let org = self.create_default_organization().await?;
        log::info!(" Created default organization: {}", org.name);

        let user = self.create_default_user(&org.id).await?;
        log::info!(" Created default user: {}", user.username);

        let (project_id, client_id, client_secret) = self.create_oauth_application(&org.id).await?;
        log::info!(" Created OAuth2 application");

        self.grant_user_permissions(&org.id, &user.id).await?;
        log::info!(" Granted admin permissions to default user");

        let config = DirectoryConfig {
            base_url: self.base_url.clone(),
            default_org: org,
            default_user: user,
            admin_token: self.admin_token.clone().unwrap_or_default(),
            project_id,
            client_id,
            client_secret,
        };

        self.save_config_internal(&config).await?;
        log::info!(" Saved Directory configuration");

        log::info!(" Directory initialization complete!");
        log::info!("");
        log::info!("╔══════════════════════════════════════════════════════════════╗");
        log::info!("║                    DEFAULT CREDENTIALS                       ║");
        log::info!("╠══════════════════════════════════════════════════════════════╣");
        log::info!("║  Email:    {:<50}║", config.default_user.email);
        log::info!("║  Password: {:<50}║", config.default_user.password);
        log::info!("╠══════════════════════════════════════════════════════════════╣");
        log::info!("║  Login at: {:<50}║", self.base_url);
        log::info!("╚══════════════════════════════════════════════════════════════╝");
        log::info!("");
        log::info!(">>> COPY THESE CREDENTIALS NOW - Press ENTER to continue <<<");

        let mut input = String::new();
        let _ = std::io::stdin().read_line(&mut input);

        Ok(config)
    }

    pub async fn create_organization(&mut self, name: &str, description: &str) -> Result<String> {
        self.ensure_admin_token()?;

        let response = self
            .client
            .post(format!("{}/management/v1/orgs", self.base_url))
            .bearer_auth(self.admin_token.as_ref().unwrap_or(&String::new()))
            .json(&json!({
                "name": name,
                "description": description,
            }))
            .send()
            .await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            anyhow::bail!("Failed to create organization: {}", error_text);
        }

        let result: serde_json::Value = response.json().await?;
        Ok(result["id"].as_str().unwrap_or("").to_string())
    }

    async fn create_default_organization(&self) -> Result<DefaultOrganization> {
        let org_name = "BotServer".to_string();

        let response = self
            .client
            .post(format!("{}/management/v1/orgs", self.base_url))
            .bearer_auth(self.admin_token.as_ref().unwrap_or(&String::new()))
            .json(&json!({
                "name": org_name,
            }))
            .send()
            .await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            anyhow::bail!("Failed to create organization: {}", error_text);
        }

        let result: serde_json::Value = response.json().await?;

        Ok(DefaultOrganization {
            id: result["id"].as_str().unwrap_or("").to_string(),
            name: org_name.clone(),
            domain: format!("{}.localhost", org_name.to_lowercase()),
        })
    }

    pub async fn create_user(
        &mut self,
        org_id: &str,
        username: &str,
        email: &str,
        password: &str,
        first_name: &str,
        last_name: &str,
        is_admin: bool,
    ) -> Result<DefaultUser> {
        self.ensure_admin_token()?;

        let response = self
            .client
            .post(format!("{}/management/v1/users/human", self.base_url))
            .bearer_auth(self.admin_token.as_ref().unwrap_or(&String::new()))
            .json(&json!({
                "userName": username,
                "profile": {
                    "firstName": first_name,
                    "lastName": last_name,
                    "displayName": format!("{} {}", first_name, last_name)
                },
                "email": {
                    "email": email,
                    "isEmailVerified": true
                },
                "password": password,
                "organisation": {
                    "orgId": org_id
                }
            }))
            .send()
            .await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            anyhow::bail!("Failed to create user: {}", error_text);
        }

        let result: serde_json::Value = response.json().await?;

        let user = DefaultUser {
            id: result["userId"].as_str().unwrap_or("").to_string(),
            username: username.to_string(),
            email: email.to_string(),
            password: password.to_string(),
            first_name: first_name.to_string(),
            last_name: last_name.to_string(),
        };

        if is_admin {
            self.grant_user_permissions(org_id, &user.id).await?;
        }

        Ok(user)
    }

    async fn create_default_user(&self, org_id: &str) -> Result<DefaultUser> {
        let username = format!(
            "admin_{}",
            uuid::Uuid::new_v4()
                .to_string()
                .chars()
                .take(8)
                .collect::<String>()
        );
        let email = format!("{}@botserver.local", username);
        let password = Self::generate_secure_password();

        let response = self
            .client
            .post(format!("{}/management/v1/users/human", self.base_url))
            .bearer_auth(self.admin_token.as_ref().unwrap_or(&String::new()))
            .json(&json!({
                "userName": username,
                "profile": {
                    "firstName": "Admin",
                    "lastName": "User",
                    "displayName": "Administrator"
                },
                "email": {
                    "email": email,
                    "isEmailVerified": true
                },
                "password": password,
                "organisation": {
                    "orgId": org_id
                }
            }))
            .send()
            .await?;

        if !response.status().is_success() {
            let error_text = response.text().await?;
            anyhow::bail!("Failed to create user: {}", error_text);
        }

        let result: serde_json::Value = response.json().await?;

        Ok(DefaultUser {
            id: result["userId"].as_str().unwrap_or("").to_string(),
            username: username.clone(),
            email: email.clone(),
            password: password.clone(),
            first_name: "Admin".to_string(),
            last_name: "User".to_string(),
        })
    }

    pub async fn create_oauth_application(
        &self,
        _org_id: &str,
    ) -> Result<(String, String, String)> {
        let app_name = "BotServer";
        let redirect_uri = "http://localhost:8080/auth/callback".to_string();

        let project_response = self
            .client
            .post(format!("{}/management/v1/projects", self.base_url))
            .bearer_auth(self.admin_token.as_ref().unwrap_or(&String::new()))
            .json(&json!({
                "name": app_name,
            }))
            .send()
            .await?;

        let project_result: serde_json::Value = project_response.json().await?;
        let project_id = project_result["id"].as_str().unwrap_or("").to_string();

        let app_response = self.client
            .post(format!("{}/management/v1/projects/{}/apps/oidc", self.base_url, project_id))
            .bearer_auth(self.admin_token.as_ref().unwrap_or(&String::new()))
            .json(&json!({
                "name": app_name,
                "redirectUris": [redirect_uri],
                "responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
                "grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE", "OIDC_GRANT_TYPE_REFRESH_TOKEN"],
                "appType": "OIDC_APP_TYPE_WEB",
                "authMethodType": "OIDC_AUTH_METHOD_TYPE_BASIC",
                "postLogoutRedirectUris": ["http://localhost:8080"],
            }))
            .send()
            .await?;

        let app_result: serde_json::Value = app_response.json().await?;
        let client_id = app_result["clientId"].as_str().unwrap_or("").to_string();
        let client_secret = app_result["clientSecret"]
            .as_str()
            .unwrap_or("")
            .to_string();

        Ok((project_id, client_id, client_secret))
    }

    pub async fn grant_user_permissions(&self, org_id: &str, user_id: &str) -> Result<()> {
        let _response = self
            .client
            .post(format!(
                "{}/management/v1/orgs/{}/members",
                self.base_url, org_id
            ))
            .bearer_auth(self.admin_token.as_ref().unwrap_or(&String::new()))
            .json(&json!({
                "userId": user_id,
                "roles": ["ORG_OWNER"]
            }))
            .send()
            .await?;

        Ok(())
    }

    pub async fn save_config(
        &mut self,
        org_id: String,
        org_name: String,
        admin_user: DefaultUser,
        client_id: String,
        client_secret: String,
    ) -> Result<DirectoryConfig> {
        self.ensure_admin_token()?;

        let config = DirectoryConfig {
            base_url: self.base_url.clone(),
            default_org: DefaultOrganization {
                id: org_id,
                name: org_name.clone(),
                domain: format!("{}.localhost", org_name.to_lowercase()),
            },
            default_user: admin_user,
            admin_token: self.admin_token.clone().unwrap_or_default(),
            project_id: String::new(),
            client_id,
            client_secret,
        };

        let json = serde_json::to_string_pretty(&config)?;
        fs::write(&self.config_path, json).await?;

        log::info!(
            "Saved Directory configuration to {}",
            self.config_path.display()
        );
        Ok(config)
    }

    async fn save_config_internal(&self, config: &DirectoryConfig) -> Result<()> {
        let json = serde_json::to_string_pretty(config)?;
        fs::write(&self.config_path, json).await?;
        Ok(())
    }

    async fn load_existing_config(&self) -> Result<DirectoryConfig> {
        let content = fs::read_to_string(&self.config_path).await?;
        let config: DirectoryConfig = serde_json::from_str(&content)?;
        Ok(config)
    }

    pub async fn get_config(&self) -> Result<DirectoryConfig> {
        self.load_existing_config().await
    }
}

pub async fn generate_directory_config(config_path: PathBuf, _db_path: PathBuf) -> Result<()> {
    let yaml_config = r"
Log:
  Level: info

Database:
  Postgres:
    Host: localhost
    Port: 5432
    Database: zitadel
    User: zitadel
    Password: zitadel
    SSL:
      Mode: disable

Machine:
  Identification:
    Hostname: localhost
    WebhookAddress: http://localhost:8080

ExternalDomain: localhost:8080
ExternalPort: 8080
ExternalSecure: false

TLS:
  Enabled: false
"
    .to_string();

    fs::write(config_path, yaml_config).await?;
    Ok(())
}
