use anyhow::Result;
use serde::Serialize;
use sqlx::SqliteConnection;

use crate::cli::{ProjectCommand, ProjectPathSubcommand, ProjectSubcommand, SearchArgs};
use crate::operations::{
    add_project_path_operation, create_project_operation, delete_project_operation,
    list_project_paths_operation, remove_project_path_operation, rename_project_operation,
};
use crate::projects::list_projects;
use crate::render::{changed_text, quote};

pub(crate) async fn cmd_project(conn: &mut SqliteConnection, args: ProjectCommand) -> Result<()> {
    match args.command {
        ProjectSubcommand::Create { name, path } => {
            let outcome = create_project_operation(conn, &name, path.as_deref()).await?;
            let project = outcome.project;
            println!(
                "created-project {} prefix={} name={}",
                project.key,
                project.prefix,
                quote(&project.name)
            );
        }
        ProjectSubcommand::Delete { project } => {
            let workspace = crate::workspaces::active_workspace();
            let outcome = delete_project_operation(conn, &workspace, &project).await?;
            println!(
                "deleted-project {} prefix={} name={}",
                outcome.project.key,
                outcome.project.prefix,
                quote(&outcome.project.name)
            );
        }
        ProjectSubcommand::List(args) => cmd_projects(conn, args).await?,
        ProjectSubcommand::Rename {
            project,
            new_name,
            prefix,
        } => {
            let workspace = crate::workspaces::active_workspace();
            let outcome =
                rename_project_operation(conn, &workspace, &project, &new_name, prefix.as_deref())
                    .await?;
            println!(
                "renamed-project {} changed={} old={} old_prefix={} prefix={} name={}",
                outcome.project.key,
                changed_text(outcome.changed),
                outcome.previous.key,
                outcome.previous.prefix,
                outcome.project.prefix,
                quote(&outcome.project.name)
            );
            if outcome.changed && outcome.config_mapping {
                println!("updated-config-project-mapping {}", outcome.project.key);
            }
        }
        ProjectSubcommand::Path { command } => match command {
            ProjectPathSubcommand::Add { project, path } => {
                let outcome = add_project_path_operation(conn, &project, &path).await?;
                println!(
                    "added-project-path {} path={} config={}",
                    outcome.project.key,
                    quote(&outcome.path),
                    quote(&outcome.config_path.display().to_string())
                );
            }
            ProjectPathSubcommand::Remove { project, path } => {
                let outcome = remove_project_path_operation(conn, &project, &path).await?;
                println!(
                    "removed-project-path {} path={} config={}",
                    outcome.project.key,
                    quote(&outcome.path),
                    quote(&outcome.config_path.display().to_string())
                );
            }
            ProjectPathSubcommand::List { project } => {
                let paths = list_project_paths_operation(conn, project.as_deref()).await?;
                for item in paths {
                    println!("{} path={}", item.project.key, quote(&item.path));
                }
            }
        },
    }
    Ok(())
}

#[derive(Serialize)]
struct ProjectListJsonItem {
    key: String,
    name: String,
    prefix: String,
}

async fn cmd_projects(conn: &mut SqliteConnection, args: SearchArgs) -> Result<()> {
    let mut projects = list_projects(conn, args.search.as_deref()).await?;
    if let Some(limit) = args.limit {
        projects.truncate(limit);
    }
    if args.json {
        let items: Vec<ProjectListJsonItem> = projects
            .into_iter()
            .map(|p| ProjectListJsonItem {
                key: p.key,
                name: p.name,
                prefix: p.prefix,
            })
            .collect();
        serde_json::to_writer_pretty(std::io::stdout(), &items)?;
        println!();
    } else {
        for project in projects {
            println!(
                "{} prefix={} name={}",
                project.key,
                project.prefix,
                quote(&project.name)
            );
        }
    }
    Ok(())
}
