use fabro_devcontainer::{Command, DevcontainerResolver};

use super::helpers::fixture_path;

#[tokio::test]
async fn resolve_image_only() {
    let config = DevcontainerResolver::resolve(&fixture_path("image-only"))
        .await
        .unwrap();

    assert!(
        config
            .dockerfile
            .contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
    );
    assert_eq!(config.remote_user.as_deref(), Some("vscode"));
    assert_eq!(config.forwarded_ports, vec![3000, 80, 9090]);
    assert_eq!(
        config.environment.get("EDITOR").map(String::as_str),
        Some("code")
    );
    assert_eq!(config.workspace_folder, "/workspaces/image-only");
    assert!(config.compose_files.is_empty());
    assert!(config.compose_service.is_none());

    assert_eq!(config.post_create_commands.len(), 1);
    assert!(matches!(&config.post_create_commands[0], Command::Shell(s) if s == "echo hello"));

    // onCreateCommand
    assert_eq!(config.on_create_commands.len(), 1);
    assert!(matches!(&config.on_create_commands[0], Command::Shell(s) if s == "setup.sh"));

    // containerEnv baked into Dockerfile
    assert!(
        config
            .dockerfile
            .contains("ENV DEBIAN_FRONTEND=noninteractive")
    );
    assert_eq!(
        config
            .container_env
            .get("DEBIAN_FRONTEND")
            .map(String::as_str),
        Some("noninteractive")
    );
}

#[tokio::test]
async fn resolve_dockerfile_mode() {
    let config = DevcontainerResolver::resolve(&fixture_path("dockerfile-mode"))
        .await
        .unwrap();

    // Should read the actual Dockerfile content
    assert!(config.dockerfile.contains("FROM node:20"));
    assert!(config.dockerfile.contains("apt-get update"));
    assert_eq!(config.remote_user.as_deref(), Some("developer"));
    assert_eq!(config.forwarded_ports, vec![4000]);

    assert_eq!(config.post_create_commands.len(), 1);
    assert!(matches!(&config.post_create_commands[0], Command::Shell(s) if s == "npm install"));

    // build.args
    assert_eq!(
        config.build_args.get("NODE_VERSION").map(String::as_str),
        Some("20")
    );

    // build.target
    assert_eq!(config.build_target.as_deref(), Some("dev"));
}

#[tokio::test]
async fn resolve_compose_mode() {
    let config = DevcontainerResolver::resolve(&fixture_path("compose-mode"))
        .await
        .unwrap();

    // In compose mode, the dockerfile is derived from the compose service's image
    assert!(config.dockerfile.contains("FROM node:20"));
    assert_eq!(config.workspace_folder, "/workspace");
    assert_eq!(config.remote_user.as_deref(), Some("node"));
    assert_eq!(config.compose_files.len(), 1);
    assert_eq!(config.compose_service.as_deref(), Some("app"));

    // Ports come from compose + forwardPorts merged
    assert_eq!(config.forwarded_ports, vec![3000, 9229, 5173]);

    // Environment merged from compose + remoteEnv
    assert_eq!(
        config.environment.get("NODE_ENV").map(String::as_str),
        Some("development")
    );
    assert_eq!(
        config.environment.get("DEBUG").map(String::as_str),
        Some("true")
    );
}

#[tokio::test]
async fn resolve_variables() {
    let config = DevcontainerResolver::resolve(&fixture_path("variables"))
        .await
        .unwrap();

    assert_eq!(config.workspace_folder, "/workspaces/variables");
    assert_eq!(
        config.environment.get("PROJECT_ROOT").map(String::as_str),
        Some("/workspaces/variables")
    );
    assert_eq!(
        config.environment.get("PROJECT_NAME").map(String::as_str),
        Some("variables")
    );
}

#[tokio::test]
async fn resolve_compose_multi() {
    let config = DevcontainerResolver::resolve(&fixture_path("compose-multi"))
        .await
        .unwrap();

    // Override file wins for image
    assert!(config.dockerfile.contains("FROM node:22"));
    assert_eq!(config.workspace_folder, "/workspace");
    assert_eq!(config.compose_files.len(), 2);
    assert_eq!(config.compose_service.as_deref(), Some("app"));

    // Port from base.yml
    assert_eq!(config.forwarded_ports, vec![3000]);

    // Environment from override.yml
    assert_eq!(
        config.environment.get("OVERRIDE_VAR").map(String::as_str),
        Some("true")
    );
}

#[tokio::test]
async fn resolve_not_found() {
    let result = DevcontainerResolver::resolve(&fixture_path("nonexistent")).await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert!(err.to_string().contains("no devcontainer.json found"));
}

#[tokio::test]
async fn resolve_subdirectory_mode() {
    let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-mode"))
        .await
        .unwrap();

    assert!(
        config
            .dockerfile
            .contains("FROM mcr.microsoft.com/devcontainers/python:3.12")
    );
    assert_eq!(config.remote_user.as_deref(), Some("vscode"));
    assert_eq!(config.workspace_folder, "/workspaces/subdirectory-mode");
}

#[tokio::test]
async fn resolve_subdirectory_multiple_picks_alphabetical_first() {
    let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-multiple"))
        .await
        .unwrap();

    // "alpha" sorts before "beta", so alpha's config is used
    assert!(
        config
            .dockerfile
            .contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
    );
    assert_eq!(config.remote_user.as_deref(), Some("alpha-user"));
}

#[tokio::test]
async fn resolve_subdirectory_standard_wins_over_subdirs() {
    let config = DevcontainerResolver::resolve(&fixture_path("subdirectory-with-standard"))
        .await
        .unwrap();

    // Standard .devcontainer/devcontainer.json takes priority over subdirectory
    // format
    assert!(
        config
            .dockerfile
            .contains("FROM mcr.microsoft.com/devcontainers/base:ubuntu")
    );
    assert_eq!(config.remote_user.as_deref(), Some("standard-user"));
}

#[tokio::test]
async fn generated_dockerfile_is_well_formed() {
    let config = DevcontainerResolver::resolve(&fixture_path("image-only"))
        .await
        .unwrap();

    // Should start with the generated header
    assert!(
        config
            .dockerfile
            .contains("# Generated by fabro-devcontainer")
    );
    // Should have the base image
    assert!(config.dockerfile.contains("FROM"));
    // Should end with a newline
    assert!(config.dockerfile.ends_with('\n'));
}
