use clap::{Parser, Subcommand};

mod commands;
mod config;
mod credentials;
mod github;
mod package_manifest;
mod pnpm;
mod project_config;
mod workspace_config;

#[derive(Parser)]
#[command(
    name = "cufflink",
    about = "Cufflink CLI — Deploy CRUD microservices in seconds"
)]
struct Cli {
    /// Target environment (from Cufflink.toml)
    #[arg(short, long, global = true)]
    env: Option<String>,

    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Initialize a new cufflink service project
    Init {
        /// Service name
        name: String,
        /// Template to use (todo, blog, ecommerce, cms)
        #[arg(short, long)]
        template: Option<String>,
        /// Service mode: crud (default) or wasm
        #[arg(short, long, default_value = "crud")]
        mode: String,
        /// Initialize from a GitHub starter kit (e.g., github:owner/repo)
        #[arg(long)]
        from: Option<String>,
    },
    /// List available service templates
    Templates,
    /// Install a package from GitHub or local path
    Install {
        /// Package source (e.g., github:owner/repo, github:owner/repo@v1.0.0, or /local/path)
        source: String,
    },
    /// Deploy all components in an installed package
    DeployPackage {
        /// Package name (as listed in cufflink-packages/)
        name: String,
        /// Skip specific components by name (repeatable)
        #[arg(long, value_delimiter = ',')]
        skip: Option<Vec<String>>,
    },
    /// Deploy a service to the platform
    Deploy {
        /// Allow destructive schema changes
        #[arg(long)]
        allow_destructive: bool,
        /// Git commit hash to associate with this deployment (auto-detected if omitted)
        #[arg(long)]
        commit_hash: Option<String>,
        /// Deploy to a specific tenant (overrides Cufflink.toml)
        #[arg(long)]
        tenant: Option<String>,
    },
    /// Rollback to a previous deployment version
    Rollback {
        /// Target version (defaults to previous)
        #[arg(short, long)]
        version: Option<i32>,
        /// Allow destructive schema changes (dropping tables/columns)
        #[arg(long)]
        allow_destructive: bool,
    },
    /// Show service status
    Status,
    /// Stream service logs
    Logs {
        /// Follow log output in real-time
        #[arg(short, long)]
        follow: bool,
        /// Show logs from this duration ago (e.g., 1h, 30m, 24h)
        #[arg(long)]
        since: Option<String>,
        /// Start time (RFC3339)
        #[arg(long)]
        from: Option<String>,
        /// End time (RFC3339)
        #[arg(long)]
        to: Option<String>,
        /// Filter logs containing this text
        #[arg(short, long)]
        search: Option<String>,
        /// Filter by log level (debug, info, warn, error)
        #[arg(short, long)]
        level: Option<String>,
        /// Maximum number of log lines (default: 100)
        #[arg(short = 'n', long)]
        limit: Option<usize>,
        /// Save logs to a file
        #[arg(short, long)]
        output: Option<String>,
    },
    /// List all deployed services
    Services {
        #[command(subcommand)]
        action: Option<ServicesAction>,
    },
    /// Print or save the OpenAPI spec for the current service
    Openapi {
        /// Write spec to a file instead of stdout
        #[arg(short, long)]
        output: Option<String>,
        /// Generate locally from the manifest (no deployment required)
        #[arg(long)]
        local: bool,
        /// Tenant slug for URL paths (used with --local, default: "local")
        #[arg(long)]
        tenant_slug: Option<String>,
    },
    /// Generate a TypeScript client from the OpenAPI spec
    GenerateClient {
        /// Output directory (default: ./generated)
        #[arg(short, long, default_value = "./generated")]
        output: String,
        /// Generate locally from the manifest (no deployment required)
        #[arg(long)]
        local: bool,
        /// Tenant slug for URL paths (used with --local, default: "local")
        #[arg(long)]
        tenant_slug: Option<String>,
    },
    /// Manage tenants (platform admin only)
    Tenants {
        /// Platform API URL (overrides Cufflink.toml and CUFFLINK_API_URL)
        #[arg(long)]
        api_url: Option<String>,
        #[command(subcommand)]
        action: TenantsAction,
    },
    /// Authenticate with the platform
    Login,
    /// Run tests (hash-based, only changed)
    Test {
        /// Run all tests regardless of hash
        #[arg(long)]
        all: bool,
    },
    /// Seed data into a deployed service from a JSON file
    Seed {
        /// Path to seed JSON file (default: seed.json)
        #[arg(default_value = "seed.json")]
        file: String,
        /// Clear existing data before seeding
        #[arg(long)]
        clear: bool,
        /// Only seed specific tables (comma-separated)
        #[arg(long, value_delimiter = ',')]
        tables: Option<Vec<String>>,
    },
    /// Manage service configuration
    Config {
        #[command(subcommand)]
        action: ConfigAction,
    },
    /// Backup and restore service data
    Backup {
        #[command(subcommand)]
        action: BackupAction,
    },
    /// Workspace-wide deploy, test, seed, and status
    Workspace {
        #[command(subcommand)]
        action: WorkspaceAction,
    },
    /// Generate CI/CD pipeline configuration
    Ci {
        #[command(subcommand)]
        action: CiAction,
    },
    /// Manage roles and role assignments
    Roles {
        #[command(subcommand)]
        action: RolesAction,
    },
    /// Manage encrypted secrets (securestore)
    Secrets {
        #[command(subcommand)]
        action: SecretsAction,
    },
}

#[derive(Subcommand)]
enum ConfigAction {
    /// List all config values
    List,
    /// Set a config value
    Set {
        /// Config key
        key: String,
        /// Config value
        value: String,
        /// Mark as secret (value masked in API responses)
        #[arg(long)]
        secret: bool,
    },
    /// Delete a config value
    Delete {
        /// Config key to delete
        key: String,
    },
    /// Sync configs and secrets from Cufflink.toml to the platform
    Sync,
}

#[derive(Subcommand)]
enum SecretsAction {
    /// Initialize a new secrets vault for an environment
    Init,
    /// Set a secret value
    Set {
        /// Secret name (e.g., "keycloak:client_secret")
        name: String,
        /// Secret value
        value: String,
    },
    /// List secret names (values are not shown unless --reveal is used)
    List {
        /// Show secret values
        #[arg(long)]
        reveal: bool,
    },
    /// Delete a secret from the vault
    Delete {
        /// Secret name to delete
        name: String,
    },
}

#[derive(Subcommand)]
enum ServicesAction {
    /// Delete a deployed service and all its data
    Delete {
        /// Service name to delete
        name: String,
        /// Skip confirmation prompt
        #[arg(long)]
        yes: bool,
    },
    /// Enable a service (make it active and routable)
    Enable {
        /// Service name
        name: String,
    },
    /// Disable a service (make it inactive — stops routing traffic)
    Disable {
        /// Service name
        name: String,
    },
}

#[derive(Subcommand)]
enum BackupAction {
    /// Show service tables, columns, and FK dependencies
    Schema,
    /// Export service data to a file
    Export {
        /// Output file path
        #[arg(short, long, default_value = "backup.jsonl")]
        output: String,
        /// Export only specific tables (comma-separated)
        #[arg(long, value_delimiter = ',')]
        tables: Option<Vec<String>>,
        /// Return job ID immediately without waiting
        #[arg(long, rename_all = "kebab-case")]
        r#async: bool,
    },
    /// Restore service data from a file or a previous backup
    Restore {
        /// Input file path (optional if --from-backup is used)
        file: Option<String>,
        /// Restore directly from a previous backup job ID (skip local file)
        #[arg(long)]
        from_backup: Option<String>,
        /// Restore only specific tables (comma-separated)
        #[arg(long, value_delimiter = ',')]
        tables: Option<Vec<String>>,
        /// Clear existing data before restoring
        #[arg(long)]
        clear: bool,
        /// Skip schema and FK validation checks
        #[arg(long)]
        force: bool,
        /// Return job ID immediately without waiting
        #[arg(long, rename_all = "kebab-case")]
        r#async: bool,
    },
    /// List completed backups available for restore
    List,
    /// List all backup/restore jobs (including in-progress and failed)
    Jobs,
    /// Cancel a running backup/restore job
    Cancel {
        /// Job ID to cancel
        job_id: String,
    },
}

#[derive(Subcommand)]
enum WorkspaceAction {
    /// Deploy all services for the current environment
    Deploy {
        /// Skip specific services (comma-separated)
        #[arg(long, value_delimiter = ',')]
        skip: Option<Vec<String>>,
        /// Deploy to a specific tenant (overrides Cufflink.toml)
        #[arg(long)]
        tenant: Option<String>,
        /// Deploy services in parallel
        #[arg(long)]
        parallel: bool,
    },
    /// Run tests for all services
    Test {
        /// Skip specific services (comma-separated)
        #[arg(long, value_delimiter = ',')]
        skip: Option<Vec<String>>,
        /// Show full test output
        #[arg(long)]
        all: bool,
    },
    /// Show deploy status of all services
    Status,
    /// Seed data for configured services
    Seed,
    /// Create or destroy a preview environment
    Preview {
        #[command(subcommand)]
        action: PreviewAction,
    },
}

#[derive(Subcommand)]
enum PreviewAction {
    /// Create a preview environment (new tenant + deploy + clone data)
    Create {
        /// Preview name (used as tenant slug: preview-<name>)
        #[arg(long)]
        name: String,
        /// Source tenant to clone data from
        #[arg(long, default_value = "default")]
        source_tenant: String,
    },
    /// Destroy a preview environment
    Destroy {
        /// Preview name
        #[arg(long)]
        name: String,
    },
}

#[derive(Subcommand)]
enum CiAction {
    /// Generate GitHub Actions workflow for deploy pipeline
    Init {
        /// CI provider (github)
        #[arg(long, default_value = "github")]
        provider: String,
    },
}

#[derive(Subcommand)]
enum TenantsAction {
    /// Create a new tenant
    Create {
        /// Tenant display name
        #[arg(long)]
        name: String,
        /// Tenant slug (URL-safe identifier)
        #[arg(long)]
        slug: String,
        /// Keycloak URL for this tenant's realm
        #[arg(long)]
        tenant_keycloak_url: Option<String>,
        /// Keycloak realm name for this tenant
        #[arg(long)]
        tenant_keycloak_realm: Option<String>,
        /// Required Keycloak realm role for deploy access (default: cufflink-deploy)
        #[arg(long)]
        deploy_role: Option<String>,
    },
    /// Update a tenant's configuration
    Update {
        /// Tenant slug to update
        slug: String,
        /// New display name
        #[arg(long)]
        name: Option<String>,
        /// Keycloak URL for this tenant's realm
        #[arg(long)]
        tenant_keycloak_url: Option<String>,
        /// Keycloak realm name for this tenant
        #[arg(long)]
        tenant_keycloak_realm: Option<String>,
        /// Required Keycloak realm role for deploy access
        #[arg(long)]
        deploy_role: Option<String>,
        /// Keycloak client ID for admin API operations (user search)
        #[arg(long)]
        keycloak_ops_client_id: Option<String>,
        /// Keycloak client secret for admin API operations (user search)
        #[arg(long)]
        keycloak_ops_client_secret: Option<String>,
    },
    /// List all tenants
    List,
    /// Delete a tenant and all its data
    Delete {
        /// Tenant slug to delete
        slug: String,
        /// Skip confirmation prompt
        #[arg(long)]
        yes: bool,
    },
    /// Create a platform admin API key
    CreateApiKey {
        /// Key name (for identification)
        #[arg(long)]
        name: String,
    },
    /// List platform admin API keys
    ListApiKeys,
    /// Revoke a platform admin API key
    RevokeApiKey {
        /// API key ID to revoke
        id: String,
    },
}

#[derive(Subcommand)]
enum RolesAction {
    /// List all roles and their permissions
    List,
    /// Assign a role to a user
    Assign {
        /// User email or username to search for
        #[arg(long)]
        user: String,
        /// Role name to assign
        #[arg(long)]
        role: String,
    },
    /// Remove a role from a user
    Unassign {
        /// User email or username to search for
        #[arg(long)]
        user: String,
        /// Role name to remove
        #[arg(long)]
        role: String,
    },
}

#[tokio::main]
async fn main() -> eyre::Result<()> {
    let cli = Cli::parse();
    let env = cli.env.as_deref();

    match cli.command {
        Commands::Init {
            name,
            template,
            mode,
            from,
        } => {
            commands::init::run(&name, template.as_deref(), &mode, from.as_deref()).await?;
        }
        Commands::Install { source } => {
            commands::install::run(&source).await?;
        }
        Commands::DeployPackage { name, skip } => {
            let skip_list = skip.unwrap_or_default();
            commands::deploy_package::run(&name, &skip_list, env).await?;
        }
        Commands::Templates => {
            commands::init::list_templates();
        }
        Commands::Deploy {
            allow_destructive,
            commit_hash,
            tenant,
        } => {
            commands::deploy::run(allow_destructive, commit_hash, env, tenant.as_deref()).await?;
        }
        Commands::Rollback {
            version,
            allow_destructive,
        } => {
            commands::rollback::run(version, allow_destructive, env).await?;
        }
        Commands::Status => {
            commands::status::run(env).await?;
        }
        Commands::Logs {
            follow,
            since,
            from,
            to,
            search,
            level,
            limit,
            output,
        } => {
            commands::logs::run(
                commands::logs::LogParams {
                    follow,
                    since: since.as_deref(),
                    from: from.as_deref(),
                    to: to.as_deref(),
                    search: search.as_deref(),
                    level: level.as_deref(),
                    limit,
                    output: output.as_deref(),
                },
                env,
            )
            .await?;
        }
        Commands::Services { action } => match action {
            None => commands::services::run(env).await?,
            Some(ServicesAction::Delete { name, yes }) => {
                commands::services::delete(&name, yes, env).await?
            }
            Some(ServicesAction::Enable { name }) => {
                commands::services::set_status(&name, "active", env).await?
            }
            Some(ServicesAction::Disable { name }) => {
                commands::services::set_status(&name, "inactive", env).await?
            }
        },
        Commands::Openapi {
            output,
            local,
            tenant_slug,
        } => {
            if let Some(path) = output {
                commands::openapi::save(&path, local, tenant_slug.as_deref(), env).await?;
            } else {
                commands::openapi::run(local, tenant_slug.as_deref(), env).await?;
            }
        }
        Commands::GenerateClient {
            output,
            local,
            tenant_slug,
        } => {
            commands::openapi::generate_client(&output, local, tenant_slug.as_deref(), env).await?;
        }
        Commands::Tenants { action, api_url } => match action {
            TenantsAction::Create {
                name,
                slug,
                tenant_keycloak_url,
                tenant_keycloak_realm,
                deploy_role,
            } => {
                commands::tenants::create(
                    &name,
                    &slug,
                    tenant_keycloak_url.as_deref(),
                    tenant_keycloak_realm.as_deref(),
                    deploy_role.as_deref(),
                    api_url.as_deref(),
                    env,
                )
                .await?
            }
            TenantsAction::Update {
                slug,
                name,
                tenant_keycloak_url,
                tenant_keycloak_realm,
                deploy_role,
                keycloak_ops_client_id,
                keycloak_ops_client_secret,
            } => {
                commands::tenants::update(
                    &slug,
                    name.as_deref(),
                    tenant_keycloak_url.as_deref(),
                    tenant_keycloak_realm.as_deref(),
                    deploy_role.as_deref(),
                    keycloak_ops_client_id.as_deref(),
                    keycloak_ops_client_secret.as_deref(),
                    api_url.as_deref(),
                    env,
                )
                .await?
            }
            TenantsAction::List => commands::tenants::list(api_url.as_deref(), env).await?,
            TenantsAction::Delete { slug, yes } => {
                commands::tenants::delete(&slug, yes, api_url.as_deref(), env).await?
            }
            TenantsAction::CreateApiKey { name } => {
                commands::tenants::create_platform_api_key(&name, api_url.as_deref(), env).await?
            }
            TenantsAction::ListApiKeys => {
                commands::tenants::list_platform_api_keys(api_url.as_deref(), env).await?
            }
            TenantsAction::RevokeApiKey { id } => {
                commands::tenants::revoke_platform_api_key(&id, api_url.as_deref(), env).await?
            }
        },
        Commands::Login => {
            commands::login::run(env).await?;
        }
        Commands::Test { all } => {
            commands::test::run(all, env).await?;
        }
        Commands::Seed {
            file,
            clear,
            tables,
        } => {
            commands::seed::run(&file, clear, tables, env).await?;
        }
        Commands::Config { action } => match action {
            ConfigAction::List => commands::config_cmd::list(env).await?,
            ConfigAction::Set { key, value, secret } => {
                commands::config_cmd::set(&key, &value, secret, env).await?
            }
            ConfigAction::Delete { key } => commands::config_cmd::delete(&key, env).await?,
            ConfigAction::Sync => commands::config_cmd::sync(env).await?,
        },
        Commands::Backup { action } => match action {
            BackupAction::Schema => commands::backup::schema(env).await?,
            BackupAction::Export {
                output,
                tables,
                r#async,
            } => commands::backup::export(&output, tables, r#async, env).await?,
            BackupAction::Restore {
                file,
                from_backup,
                tables,
                clear,
                force,
                r#async,
            } => {
                commands::backup::restore(
                    file.as_deref(),
                    from_backup.as_deref(),
                    tables,
                    clear,
                    force,
                    r#async,
                    env,
                )
                .await?
            }
            BackupAction::List => commands::backup::list(env).await?,
            BackupAction::Jobs => commands::backup::jobs(env).await?,
            BackupAction::Cancel { job_id } => commands::backup::cancel(&job_id, env).await?,
        },
        Commands::Workspace { action } => match action {
            WorkspaceAction::Deploy {
                skip,
                tenant,
                parallel,
            } => {
                let skip_list = skip.unwrap_or_default();
                commands::workspace::deploy(&skip_list, env, tenant.as_deref(), parallel).await?
            }
            WorkspaceAction::Test { skip, all } => {
                let skip_list = skip.unwrap_or_default();
                commands::workspace::test(&skip_list, all).await?
            }
            WorkspaceAction::Status => commands::workspace::status(env).await?,
            WorkspaceAction::Seed => commands::workspace::seed(env).await?,
            WorkspaceAction::Preview { action } => match action {
                PreviewAction::Create {
                    name,
                    source_tenant,
                } => commands::workspace::preview_create(&name, &source_tenant, env).await?,
                PreviewAction::Destroy { name } => {
                    commands::workspace::preview_destroy(&name, env).await?
                }
            },
        },
        Commands::Ci { action } => match action {
            CiAction::Init { provider } => commands::ci::init(&provider)?,
        },
        Commands::Secrets { action } => match action {
            SecretsAction::Init => commands::secrets_cmd::init(env)?,
            SecretsAction::Set { name, value } => commands::secrets_cmd::set(&name, &value, env)?,
            SecretsAction::List { reveal } => commands::secrets_cmd::list(env, reveal)?,
            SecretsAction::Delete { name } => commands::secrets_cmd::delete(&name, env)?,
        },
        Commands::Roles { action } => match action {
            RolesAction::List => commands::roles::list(env).await?,
            RolesAction::Assign { user, role } => {
                commands::roles::assign(&user, &role, env).await?
            }
            RolesAction::Unassign { user, role } => {
                commands::roles::unassign(&user, &role, env).await?
            }
        },
    }

    Ok(())
}
