use std::fs;
use std::path::Path;

use crust_shared_domain::bounded_context_entity::BoundedContext;
use toml_edit::{Array, DocumentMut, Item, Value};

use crust_shared_infrastructure::{
    fs_helper::{
        create_unique_temp_dir,
        finalize_scaffold,
        io_conflict,
        map_fs_error,
        remove_dir,
        FsHelperError,
    },
    FileSystemTransaction,
};

use crate::templates::crates::WORKSPACE_TEMPLATES;

use crust_workspace_domain::{
    project_entity::Project,
    errors::WorkspaceError,
};

const APPLICATION_LAYER: &str = "application";

fn context_member_paths(bounded_context_name: &str) -> [String; 3] {
    [
        format!("crates/{bounded_context_name}/domain"),
        format!("crates/{bounded_context_name}/application"),
        format!("crates/{bounded_context_name}/infrastructure"),
    ]
}

fn add_member_if_missing(members: &mut Array, member: &str, prefix: &str) -> bool {
    if members.iter().any(|value| value.as_str() == Some(member)) {
        return false;
    }

    let mut formatted = Value::from(member);
    formatted.decor_mut().set_prefix(prefix);
    formatted.decor_mut().set_suffix("");
    members.push_formatted(formatted);
    true
}

fn format_bounded_context_comment(name: &str) -> String {
    let mut chars = name.chars();
    match chars.next() {
        Some(first) => {
            let mut formatted = String::with_capacity(name.len());
            formatted.extend(first.to_uppercase());
            formatted.push_str(chars.as_str());
            formatted
        }
        None => "---".to_string(),
    }
}

fn bounded_context_member_prefix(is_first_member: bool, bounded_context_name: &str) -> String {
    if is_first_member {
        let comment = format_bounded_context_comment(bounded_context_name);
        format!("\n\n    # {comment}\n    ")
    } else {
        "\n    ".to_string()
    }
}

fn workspace_error_from_fs(error: FsHelperError) -> WorkspaceError {
    map_fs_error(
        error,
        |path, reason| WorkspaceError::ProjectStructureConflict { path, reason },
        |path, reason| WorkspaceError::InvalidProjectLayout { path, reason },
    )
}

/// Build transaction for application layer sources
fn build_application_sources_transaction(
    tx: &mut FileSystemTransaction,
    src_path: &Path,
) {
    let query_path = src_path.join("query");
    let command_path = src_path.join("command");

    tx.create_dir(&query_path);
    tx.create_dir(&command_path);
    tx.create_file(query_path.join("mod.rs"), "");
    tx.create_file(command_path.join("mod.rs"), "");
    tx.create_file(src_path.join("lib.rs"), "pub mod query;\npub mod command;\n");
}

/// Build transaction for a single layer workspace
fn build_layer_transaction(
    tx: &mut FileSystemTransaction,
    bounded_context_path: &Path,
    bounded_context: &BoundedContext,
    workspace_layer_name: &str,
    cargo_toml_template: &str,
) {
    let workspace_path = bounded_context_path.join(workspace_layer_name);
    tx.create_dir(&workspace_path);
    
    let src_path = workspace_path.join("src");
    tx.create_dir(&src_path);
    
    let cargo_toml_content =
        cargo_toml_template.replace("{bounded_context_name}", bounded_context.name().as_str());
    tx.create_file(workspace_path.join("Cargo.toml"), cargo_toml_content);

    if workspace_layer_name == APPLICATION_LAYER {
        build_application_sources_transaction(tx, &src_path);
    } else {
        tx.create_file(src_path.join("lib.rs"), "");
    }
}

/// Build a transaction for creating the entire bounded context structure
fn build_bounded_context_transaction(
    temp_root: &Path,
    bounded_context: &BoundedContext,
) -> Result<FileSystemTransaction, WorkspaceError> {
    let mut tx = FileSystemTransaction::new();

    // Create all layers
    for ws in WORKSPACE_TEMPLATES {
        build_layer_transaction(&mut tx, temp_root, bounded_context, ws.name, ws.template);
    }

    Ok(tx)
}

fn upsert_root_workspace_members(project: &Project, bounded_context: &BoundedContext) -> Result<(), WorkspaceError> {
    let cargo_toml_path = project.path().join("Cargo.toml");
    let cargo_toml_content = fs::read_to_string(&cargo_toml_path)
        .map_err(|error| workspace_error_from_fs(io_conflict(&cargo_toml_path, "read file", error)))?;

    let mut document = cargo_toml_content
        .parse::<DocumentMut>()
        .map_err(|error| WorkspaceError::InvalidProjectLayout {
            path: cargo_toml_path.clone(),
            reason: format!("Invalid root Cargo.toml: {error}"),
        })?;

    if !document["workspace"].is_table() {
        document["workspace"] = Item::Table(toml_edit::Table::new());
    }

    if !document["workspace"]["members"].is_array() {
        document["workspace"]["members"] = Item::Value(Value::Array(Array::new()));
    }

    let members = document["workspace"]["members"]
        .as_array_mut()
        .ok_or_else(|| WorkspaceError::InvalidProjectLayout {
            path: cargo_toml_path.clone(),
            reason: "[workspace].members must be an array".to_string(),
        })?;

    let new_members = context_member_paths(bounded_context.name().as_str());

    let mut inserted_any = false;
    let mut is_first_inserted = true;
    for member in new_members {
        let prefix = bounded_context_member_prefix(is_first_inserted, bounded_context.name().as_str());
        if add_member_if_missing(members, &member, &prefix) {
            inserted_any = true;
            is_first_inserted = false;
        }
    }

    if inserted_any {
        members.set_trailing_comma(true);
        members.set_trailing("\n");
    }

    fs::write(&cargo_toml_path, document.to_string())
        .map_err(|error| workspace_error_from_fs(io_conflict(&cargo_toml_path, "write file", error)))?;

    Ok(())
}

pub fn scaffold_bounded_context(
    project: &mut Project,
    bounded_context: BoundedContext,
) -> Result<(), WorkspaceError> {
    let bounded_context_path = project.ensure_can_create_bounded_context(&bounded_context)?;
    if bounded_context_path.exists() {
        return Err(WorkspaceError::BoundedContextAlreadyExists {
            path: bounded_context_path,
        });
    }

    let temp_root_parent = project.expected_crates_path();
    let temp_root = create_unique_temp_dir(&temp_root_parent, "crust-bounded-context")
        .map_err(workspace_error_from_fs)?;

    // Build and commit the transaction atomically
    let tx = build_bounded_context_transaction(&temp_root, &bounded_context)?;
    if let Err(error) = tx.commit().map_err(workspace_error_from_fs) {
        remove_dir(&temp_root);
        return Err(error);
    }

    // Finalize: move from temp to actual location
    if let Err(error) = finalize_scaffold(&temp_root, &bounded_context_path).map_err(workspace_error_from_fs) {
        remove_dir(&temp_root);
        return Err(error);
    }

    // Update root workspace members - if this fails, bounded context is left but project metadata is inconsistent
    // This is acceptable because the bounded context structure is created correctly and can be manually cleaned
    if let Err(error) = upsert_root_workspace_members(project, &bounded_context) {
        // Log a warning but don't fail completely - the bounded context exists but isn't in workspace.members
        // User can manually add it or re-run the command
        return Err(error);
    }

    project.add_bounded_context(bounded_context)
}

