use clap::{Parser, Subcommand};

mod commands;
mod json_io;

#[derive(Parser)]
#[command(
    name = "rok",
    version,
    about = "rok developer CLI for Axum applications"
)]
struct Cli {
    /// Output result as JSON (machine-readable)
    #[arg(long, short = 'j', global = true)]
    json: bool,

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

#[derive(Subcommand)]
enum Command {
    /// Scaffold a new rok/Axum application
    New {
        /// Name of the application directory to create
        name: String,
        /// Project template: api, saas, htmx, microservice, minimal (default: interactive prompt)
        #[arg(long, short = 't', value_name = "TEMPLATE")]
        template: Option<String>,
    },

    // ── make:* generators ──────────────────────────────────────────────────────
    /// Generate a controller
    #[command(name = "make:controller")]
    MakeController {
        /// Controller name (e.g. User, BlogPost)
        name: String,
    },

    /// Generate a model (and optionally a migration, controller, resource, and factory)
    #[command(name = "make:model")]
    MakeModel {
        /// Model name (e.g. User, BlogPost)
        name: String,
        /// Primary key type: ulid, cuid2, uuid_v7, snowflake, nanoid (default: bigserial i64)
        #[arg(long, value_name = "TYPE")]
        id: Option<String>,
        /// Also generate a migration for this model
        #[arg(long)]
        migration: bool,
        /// Also generate a controller for this model
        #[arg(long)]
        controller: bool,
        /// Also generate an API resource transformer for this model
        #[arg(long)]
        resource: bool,
        /// Also generate a model factory for this model
        #[arg(long)]
        factory: bool,
    },

    /// Generate a migration
    #[command(name = "make:migration")]
    MakeMigration {
        /// Migration name (e.g. create_users_table)
        name: String,
    },

    /// Generate a seeder
    #[command(name = "make:seeder")]
    MakeSeeder {
        /// Seeder name (e.g. User, BlogPost)
        name: String,
    },

    /// Generate a validator
    #[command(name = "make:validator")]
    MakeValidator {
        /// Validator name (e.g. Login, CreatePost)
        name: String,
    },

    /// Generate full CRUD scaffold: model, migration, controller, validator, resource, policy, factory, test, routes
    #[command(name = "make:crud")]
    MakeCrud {
        /// Resource name (e.g. Post, BlogArticle)
        name: String,
        /// Comma-separated fields: title:string,body:text,published:bool
        #[arg(long, value_name = "FIELDS")]
        fields: Option<String>,
        /// Primary key type: ulid, cuid2, uuid_v7, snowflake, nanoid (default: bigserial)
        #[arg(long, value_name = "TYPE")]
        id: Option<String>,
        /// Add auth guard (require authenticated user) to all routes
        #[arg(long)]
        auth: bool,
        /// Add deleted_at soft-delete column
        #[arg(long)]
        soft_delete: bool,
        /// Add created_at / updated_at timestamp columns
        #[arg(long, default_value = "true")]
        timestamps: bool,
        /// Add cursor-based pagination to index endpoint
        #[arg(long)]
        paginate: bool,
        /// Overwrite existing files
        #[arg(long)]
        force: bool,
    },

    /// Generate a model + migration from an inline JSON payload
    #[command(name = "make:from-json")]
    MakeFromJson {
        /// Resource name (e.g. Order)
        name: String,
        /// JSON string (inline) or path to a JSON file (use with --file)
        json_input: String,
        /// Treat `json` argument as a file path
        #[arg(long)]
        file: bool,
        /// Preview generated code without writing files
        #[arg(long)]
        dry_run: bool,
        /// Overwrite existing files
        #[arg(long)]
        force: bool,
    },

    /// Generate a model + migration from a JSON Schema file
    #[command(name = "make:from-schema")]
    MakeFromSchema {
        /// Resource name (e.g. Order)
        name: String,
        /// Path to the JSON Schema file
        #[arg(long, value_name = "FILE")]
        file: String,
        /// Preview generated code without writing files
        #[arg(long)]
        dry_run: bool,
        /// Overwrite existing files
        #[arg(long)]
        force: bool,
    },

    /// Generate an observer
    #[command(name = "make:observer")]
    MakeObserver {
        /// Observer name (e.g. User, Post)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate an API resource transformer
    #[command(name = "make:resource")]
    MakeResource {
        /// Resource name (e.g. User, Post)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate a query scope
    #[command(name = "make:scope")]
    MakeScope {
        /// Scope name (e.g. Active, Published)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate an authorization policy
    #[command(name = "make:policy")]
    MakePolicy {
        /// Policy name (e.g. User, Post)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate a background job
    #[command(name = "make:job")]
    MakeJob {
        /// Job name (e.g. SendWelcomeEmail)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate an event
    #[command(name = "make:event")]
    MakeEvent {
        /// Event name (e.g. UserRegistered)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate an event listener
    #[command(name = "make:listener")]
    MakeListener {
        /// Listener name (e.g. SendWelcomeEmail)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate a notification
    #[command(name = "make:notification")]
    MakeNotification {
        /// Notification name (e.g. WelcomeMail)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate a test file
    #[command(name = "make:test")]
    MakeTest {
        /// Test name (e.g. UserController)
        name: String,
        /// Overwrite existing file
        #[arg(long)]
        force: bool,
    },

    /// Generate a locale translation file from locales/en.json as template
    #[command(name = "make:locale")]
    MakeLocale {
        /// Language code to generate (e.g. fr, de, es, ja)
        lang: String,
        /// Overwrite if the locale file already exists
        #[arg(long)]
        force: bool,
    },

    /// Generate a TypeScript API client from resource structs
    #[command(name = "make:ts-client")]
    MakeTsClient {
        /// Output file path (default: client.ts)
        #[arg(long, value_name = "FILE")]
        output: Option<String>,
    },

    // ── db:* commands ──────────────────────────────────────────────────────────
    /// Apply all pending migrations
    #[command(name = "db:migrate")]
    DbMigrate {
        /// Print pending migrations without applying them
        #[arg(long)]
        dry_run: bool,
    },

    /// Rollback the last N migration batches
    #[command(name = "db:rollback")]
    DbRollback {
        /// Number of migration steps to roll back (default 1)
        #[arg(long, value_name = "N")]
        step: Option<u32>,
    },

    /// Show migration status
    #[command(name = "db:status")]
    DbStatus,

    /// Run database seeders
    #[command(name = "db:seed")]
    DbSeed {
        /// Run only this specific seeder class
        #[arg(long, value_name = "CLASS")]
        class: Option<String>,
    },

    /// Drop all tables and re-run all migrations
    #[command(name = "db:fresh")]
    DbFresh,

    /// Introspect a live database table and generate a model struct
    #[command(name = "db:pull")]
    DbPull {
        /// Table name to introspect
        table: String,
        /// Write generated model to this file instead of printing
        #[arg(long, value_name = "FILE")]
        output: Option<String>,
    },

    /// Compare a model file against the live database schema and report drift
    #[command(name = "db:diff")]
    DbDiff {
        /// Path to the model source file (e.g. src/app/models/user.rs)
        model_file: String,
        /// Override table name (default: inferred from #[rok_orm(table = "...")])
        #[arg(long, value_name = "TABLE")]
        table: Option<String>,
    },

    // ── queue:* commands ───────────────────────────────────────────────────────
    /// Start the queue worker (hint: integrate rok_queue::Worker in your app)
    #[command(name = "queue:work")]
    QueueWork {
        /// Queue name to consume (default: "default")
        #[arg(long, value_name = "QUEUE")]
        queue: Option<String>,
        /// Number of concurrent job runners (default: 4)
        #[arg(long, value_name = "N")]
        concurrency: Option<usize>,
    },

    /// Show pending/running/done/failed job counts per queue
    #[command(name = "queue:status")]
    QueueStatus,

    /// Reset a failed job back to pending
    #[command(name = "queue:retry")]
    QueueRetry {
        /// Job ID to retry
        #[arg(long, value_name = "ID")]
        job_id: i64,
    },

    /// Delete failed jobs from a queue (default: all queues)
    #[command(name = "queue:flush")]
    QueueFlush {
        /// Queue name (omit to flush all queues)
        #[arg(long, value_name = "QUEUE")]
        queue: Option<String>,
    },

    // ── key / routes / dev / env ───────────────────────────────────────────────
    /// Generate a cryptographically random JWT secret
    #[command(name = "key:generate")]
    KeyGenerate,

    /// Rotate APP_KEY / JWT_SECRET — generate a new key and optionally update .env
    #[command(name = "key:rotate")]
    KeyRotate {
        /// Write the new key to .env in-place (replaces the matching KEY= line)
        #[arg(long)]
        write: bool,
        /// Key name to rotate (default: JWT_SECRET)
        #[arg(long, value_name = "NAME", default_value = "JWT_SECRET")]
        key: String,
    },

    /// Print current environment / config values (secrets are masked)
    #[command(name = "config:show")]
    ConfigShow {
        /// Output as JSON
        #[arg(long)]
        json: bool,
    },

    /// List all registered routes
    #[command(name = "routes:list")]
    RoutesList,

    /// Start the dev server with file-watch auto-restart (requires cargo-watch)
    Dev,

    /// Start the application server (auto-reloads with cargo-watch if available)
    Serve {
        /// Port to listen on (sets LISTEN_ADDR env var)
        #[arg(long, value_name = "PORT")]
        port: Option<u16>,
        /// Build in release mode
        #[arg(long)]
        release: bool,
    },

    /// Check that all required env vars (from #[env(...)]) are set
    #[command(name = "env:check")]
    EnvCheck,

    /// Create a .rok/config.toml with generator defaults
    #[command(name = "rok:init")]
    RokInit,

    /// Launch the terminal UI dashboard (DB browser, migrations, features, queue)
    Tui {
        /// Database URL override (defaults to DATABASE_URL env var)
        #[arg(long, value_name = "URL")]
        db: Option<String>,
    },

    // ── make:scaffold / make:feature ──────────────────────────────────────────
    /// One-command feature scaffold (auth, crud, crud-api, upload, webhook, ...)
    #[command(name = "make:scaffold")]
    MakeScaffold {
        /// Template name (run without args to list all)
        template: Option<String>,
        /// Resource name (used as prefix for generated files)
        #[arg(long, short = 'n', value_name = "NAME")]
        name: Option<String>,
        /// Model name for data-centric scaffolds
        #[arg(long, short = 'm', value_name = "MODEL")]
        model: Option<String>,
        /// Preview what would be generated without writing files
        #[arg(long)]
        dry_run: bool,
        /// Read scaffold config from JSON file
        #[arg(long, short = 'J', alias = "jf", value_name = "FILE")]
        json_file: Option<String>,
        /// List all available scaffold templates
        #[arg(long, short = 'l')]
        list: bool,
    },

    /// Generate a complete feature from a JSON spec (models + controllers + routes)
    #[command(name = "make:feature")]
    MakeFeature {
        /// Inline JSON spec string
        #[arg(long, value_name = "JSON")]
        spec: Option<String>,
        /// Path to JSON spec file
        #[arg(long, short = 'J', alias = "jf", value_name = "FILE")]
        json_file: Option<String>,
        /// Preview without writing files
        #[arg(long)]
        dry_run: bool,
    },

    // ── plan:* commands ───────────────────────────────────────────────────────
    /// Show the next incomplete phase in router.md
    #[command(name = "plan:next")]
    PlanNext,

    /// List all phases with their status
    #[command(name = "plan:list")]
    PlanList,

    /// Output the dependency graph in DOT format
    #[command(name = "plan:graph")]
    PlanGraph,

    /// Show status of a specific phase (or all phases)
    #[command(name = "plan:status")]
    PlanStatus {
        /// Phase number to inspect
        #[arg(long, value_name = "N")]
        phase: Option<u32>,
    },

    // ── agent:* commands ──────────────────────────────────────────────────────
    /// Print the rok coding rules injected into AI agents
    #[command(name = "agent:rules")]
    AgentRules,

    /// Show full context (feature.md + PRD + prompt + progress) for a phase
    #[command(name = "agent:context")]
    AgentContext {
        /// Phase number
        #[arg(long, value_name = "N")]
        phase: Option<u32>,
    },

    /// Append feedback to progress.txt
    #[command(name = "agent:feedback")]
    AgentFeedback {
        /// Progress note to append
        #[arg(long, value_name = "TEXT")]
        progress: String,
        /// PRD item index to mark updated (0-based)
        #[arg(long, value_name = "N")]
        prd_item: Option<usize>,
    },

    /// Launch Claude Code with rok rules pre-injected
    #[command(name = "agent:claude")]
    AgentClaude {
        /// Phase number to provide as context
        #[arg(long, value_name = "N")]
        phase: Option<u32>,
        /// Additional prompt text
        #[arg(long, value_name = "TEXT")]
        prompt: Option<String>,
    },

    /// Launch OpenCode with rok rules pre-injected
    #[command(name = "agent:opencode")]
    AgentOpencode {
        /// Phase number to provide as context
        #[arg(long, value_name = "N")]
        phase: Option<u32>,
    },
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();
    let json_mode = cli.json;

    let result: anyhow::Result<()> = match cli.command {
        Command::New { name, template } => commands::new::run(&name, template.as_deref()),

        // ── make:* ────────────────────────────────────────────────────────────
        Command::MakeController { name } => commands::make::controller(&name),
        Command::MakeModel {
            name,
            id,
            migration,
            controller,
            resource,
            factory,
        } => commands::make::model_full(
            &name,
            id.as_deref(),
            migration,
            controller,
            resource,
            factory,
        ),
        Command::MakeMigration { name } => commands::make::migration(&name),
        Command::MakeSeeder { name } => commands::make::seeder(&name),
        Command::MakeValidator { name } => commands::make::validator(&name),
        Command::MakeObserver { name, force } => commands::make::observer(&name, force),
        Command::MakeResource { name, force } => commands::make::resource(&name, force),
        Command::MakeScope { name, force } => commands::make::scope(&name, force),
        Command::MakePolicy { name, force } => commands::make::policy(&name, force),
        Command::MakeJob { name, force } => commands::make::job(&name, force),
        Command::MakeEvent { name, force } => commands::make::event(&name, force),
        Command::MakeListener { name, force } => commands::make::listener(&name, force),
        Command::MakeNotification { name, force } => commands::make::notification(&name, force),
        Command::MakeTest { name, force } => commands::make::test(&name, force),
        Command::MakeLocale { lang, force } => commands::make::locale(&lang, force),

        Command::MakeCrud {
            name,
            fields,
            id,
            auth,
            soft_delete,
            timestamps,
            paginate,
            force,
        } => commands::crud::crud(
            &name,
            fields.as_deref(),
            id.as_deref(),
            auth,
            soft_delete,
            paginate,
            timestamps,
            force,
        ),

        Command::MakeFromJson {
            name,
            json_input,
            file,
            dry_run,
            force,
        } => commands::from_json::from_json(&name, &json_input, file, dry_run, force),

        Command::MakeFromSchema {
            name,
            file,
            dry_run,
            force,
        } => commands::from_json::from_schema(&name, &file, dry_run, force),

        Command::MakeTsClient { output } => commands::ts_client::make_ts_client(output.as_deref()),

        // ── db:* ──────────────────────────────────────────────────────────────
        Command::DbMigrate { dry_run } => commands::db::migrate(dry_run).await,
        Command::DbRollback { step } => commands::db::rollback(step.unwrap_or(1)).await,
        Command::DbStatus => commands::db::status().await,
        Command::DbSeed { class } => commands::db::seed(class.as_deref()).await,
        Command::DbFresh => commands::db::fresh().await,

        Command::DbPull { table, output } => {
            commands::inspect::pull(&table, output.as_deref()).await
        }
        Command::DbDiff { model_file, table } => {
            commands::inspect::diff(&model_file, table.as_deref()).await
        }

        // ── queue:* ───────────────────────────────────────────────────────────
        Command::QueueWork { queue, concurrency } => {
            commands::queue::work(queue.as_deref(), concurrency)
        }
        Command::QueueStatus => commands::queue::status().await,
        Command::QueueRetry { job_id } => commands::queue::retry(job_id).await,
        Command::QueueFlush { queue } => commands::queue::flush(queue.as_deref()).await,

        // ── utilities ─────────────────────────────────────────────────────────
        Command::KeyGenerate => {
            commands::key::run();
            Ok(())
        }
        Command::KeyRotate { write, key } => commands::key::rotate(&key, write),
        Command::ConfigShow { json } => commands::dev::config_show(json),
        Command::RoutesList => commands::routes::run(),
        Command::Dev => commands::dev::dev(),
        Command::Serve { port, release } => commands::dev::serve(port, release),
        Command::EnvCheck => commands::dev::env_check(),
        Command::RokInit => commands::dev::init_rok_config(),

        Command::Tui { db } => commands::tui::run(db.as_deref()),

        // ── make:scaffold / make:feature ──────────────────────────────────────
        Command::MakeScaffold {
            template,
            name,
            model,
            dry_run,
            json_file: _,
            list,
        } => {
            use commands::scaffolds::{ScaffoldArgs, ScaffoldRegistry};
            (|| -> anyhow::Result<()> {
                let reg = ScaffoldRegistry::new();
                if list || template.is_none() {
                    for (n, desc) in reg.list() {
                        println!("{:<20} {desc}", n);
                    }
                    return Ok(());
                }
                let tmpl = template.as_deref().unwrap();
                let scaffold = reg.get(tmpl).ok_or_else(|| {
                    anyhow::anyhow!("Unknown scaffold '{tmpl}' — run: rok make:scaffold --list")
                })?;
                let args = ScaffoldArgs {
                    name,
                    model,
                    fields: Vec::new(),
                    flags: Default::default(),
                    dry_run,
                    json: json_mode,
                };
                let result = scaffold.generate(&args)?;
                if json_mode {
                    result.print_json();
                } else {
                    result.print_human();
                }
                Ok(())
            })()
        }

        Command::MakeFeature {
            spec,
            json_file,
            dry_run,
        } => commands::feature::run(spec.as_deref(), json_file.as_deref(), dry_run, json_mode),

        // ── plan:* ────────────────────────────────────────────────────────────
        Command::PlanNext => commands::plan::next(json_mode),
        Command::PlanList => commands::plan::list(json_mode),
        Command::PlanGraph => commands::plan::graph(),
        Command::PlanStatus { phase } => commands::plan::status(phase, json_mode),

        // ── agent:* ───────────────────────────────────────────────────────────
        Command::AgentRules => commands::agent::rules(json_mode),
        Command::AgentContext { phase } => commands::agent::context(phase, json_mode),
        Command::AgentFeedback { progress, prd_item } => {
            commands::agent::feedback(&progress, prd_item, json_mode)
        }
        Command::AgentClaude { phase, prompt } => {
            commands::agent::launch_claude(phase, prompt.as_deref())
        }
        Command::AgentOpencode { phase } => commands::agent::launch_opencode(phase, None),
    };

    if let Err(e) = result {
        eprintln!("{} {e:#}", console::style("error:").red().bold());
        std::process::exit(1);
    }
}
