use crate::cli::{Cli, Commands, DeployCommand, DevCommand, ServiceCommand};
use crate::dev::load_env_file;
use crate::service::{CreateServiceSpec, create_service_from_template, parse_exposes};
use crate::service_local::{
    find_service_root_from, replace_localhost_port, service_dev_command, stable_port_slot,
};
use crate::util::make_vars;
use clap::Parser;
use std::env;
use std::fs;

#[test]
fn clap_parses_service_create() {
    let cli = Cli::try_parse_from([
        "exe",
        "service",
        "create",
        "--name",
        "orders",
        "--domain",
        "commerce",
        "--template",
        "rust",
    ])
    .unwrap();
    match cli.command {
        Commands::Service {
            command: ServiceCommand::Create(args),
        } => {
            assert_eq!(args.name, "orders");
            assert_eq!(args.domain, "commerce");
            assert_eq!(args.template_lang, "rust");
        }
        _ => panic!("unexpected command"),
    }
}

#[test]
fn maps_make_vars() {
    let vars = make_vars(&[
        "tag=latest".into(),
        "--image-tag=v1".into(),
        "--ignored".into(),
    ]);
    assert_eq!(vars, vec!["tag=latest", "IMAGE_TAG=v1"]);
}

#[test]
fn clap_parses_service_local_setup_and_dev() {
    let setup = Cli::try_parse_from(["exe", "setup", "SKIP_DOCKER=1"]).unwrap();
    match setup.command {
        Commands::Setup(args) => assert_eq!(args.vars, vec!["SKIP_DOCKER=1"]),
        _ => panic!("unexpected command"),
    }

    let dev = Cli::try_parse_from(["exe", "dev"]).unwrap();
    match dev.command {
        Commands::Dev { command } => assert!(command.is_none()),
        _ => panic!("unexpected command"),
    }
}

#[test]
fn clap_parses_explicit_dev_compose_file() {
    let cli = Cli::try_parse_from([
        "exe",
        "dev",
        "up",
        "DEV_COMPOSE_FILE=services/core/auth/development/compose.dev.yml",
    ])
    .unwrap();
    match cli.command {
        Commands::Dev {
            command: Some(DevCommand::Up(args)),
        } => assert_eq!(
            args.vars,
            vec!["DEV_COMPOSE_FILE=services/core/auth/development/compose.dev.yml"]
        ),
        _ => panic!("unexpected command"),
    }
}

#[test]
fn clap_parses_release_sync_deploy_command() {
    let cli = Cli::try_parse_from(["exe", "deploy", "release-sync"]).unwrap();
    match cli.command {
        Commands::Deploy {
            command: DeployCommand::ReleaseSync(args),
        } => assert!(args.vars.is_empty()),
        _ => panic!("unexpected command"),
    }
}

#[test]
fn clap_parses_top_level_release_sync_command() {
    let cli = Cli::try_parse_from(["exe", "release-sync"]).unwrap();
    match cli.command {
        Commands::ReleaseSync(args) => assert!(args.vars.is_empty()),
        _ => panic!("unexpected command"),
    }
}

#[test]
fn finds_service_root_from_nested_directory() {
    let dir = tempfile::tempdir().unwrap();
    let service_root = dir.path().join("services/core/auth");
    let nested = service_root.join("src/bootstrap");
    fs::create_dir_all(&nested).unwrap();
    fs::write(
        service_root.join("service.yaml"),
        "name: auth\nruntime: rust\n",
    )
    .unwrap();

    assert_eq!(
        find_service_root_from(&nested).unwrap(),
        service_root.to_path_buf()
    );
}

#[test]
fn chooses_dev_command_from_service_runtime() {
    let dir = tempfile::tempdir().unwrap();
    fs::write(dir.path().join("service.yaml"), "name: auth\nruntime: go\n").unwrap();
    fs::create_dir_all(dir.path().join("cmd/server")).unwrap();
    fs::write(dir.path().join("cmd/server/main.go"), "package main\n").unwrap();

    assert_eq!(
        service_dev_command(dir.path()).unwrap(),
        vec!["go", "run", "./cmd/server"]
    );
}

#[test]
fn derives_stable_service_port_slots() {
    assert_eq!(stable_port_slot("core-auth"), stable_port_slot("core-auth"));
    assert_ne!(
        stable_port_slot("core-auth"),
        stable_port_slot("core-gateway")
    );
}

#[test]
fn rewrites_localhost_url_ports() {
    assert_eq!(
        replace_localhost_port(
            "postgres://executesoft:executesoft@localhost:5432/core_auth?sslmode=disable",
            15480,
        )
        .unwrap(),
        "postgres://executesoft:executesoft@localhost:15480/core_auth?sslmode=disable"
    );
    assert_eq!(
        replace_localhost_port("redis://127.0.0.1:6379/0", 16480).unwrap(),
        "redis://127.0.0.1:16480/0"
    );
    assert_eq!(
        replace_localhost_port("mongodb://localhost:27017", 17480).unwrap(),
        "mongodb://localhost:17480"
    );
}

#[test]
fn env_file_loader_preserves_existing_values() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("app.env.example");
    let key = format!("EXE_TEST_NATS_URL_{}", std::process::id());
    fs::write(&path, format!("{key}=nats://localhost:4222\n")).unwrap();

    unsafe { env::set_var(&key, "nats://localhost:14999") };
    load_env_file(&path);
    assert_eq!(env::var(&key).unwrap(), "nats://localhost:14999");
    unsafe { env::remove_var(&key) };
}

#[test]
fn parses_exposes() {
    let dir = tempfile::tempdir().unwrap();
    let path = dir.path().join("service.yaml");
    fs::write(&path, "name: auth\nexposes:\n  - protobuf: api/auth.proto\n  - openapi: api/openapi.yaml\nruntime: rust\n").unwrap();
    let exposes = parse_exposes(&path);
    assert_eq!(exposes.get("protobuf").unwrap(), "api/auth.proto");
    assert_eq!(exposes.get("openapi").unwrap(), "api/openapi.yaml");
}

#[test]
fn help_describes_commands() {
    let mut command = <Cli as clap::CommandFactory>::command();
    let mut help = Vec::new();
    command.write_long_help(&mut help).unwrap();
    let help = String::from_utf8(help).unwrap();
    assert!(help.contains("Create, validate, build, test, and run services"));
    assert!(help.contains("Generate and validate public gateway route artifacts"));
    assert!(help.contains("Examples:"));

    let mut service = <Cli as clap::CommandFactory>::command()
        .find_subcommand_mut("service")
        .unwrap()
        .clone();
    let mut service_help = Vec::new();
    service.write_long_help(&mut service_help).unwrap();
    let service_help = String::from_utf8(service_help).unwrap();
    assert!(service_help.contains("Validate all service roots"));
    assert!(service_help.contains("Create a service from a certified template"));
}

#[test]
fn creates_service_from_template() {
    let dir = tempfile::tempdir().unwrap();
    let root = dir.path();
    let template = root.join("tools/templates/template-go-grpc");
    let skeleton = template.join("skeleton");
    fs::create_dir_all(skeleton.join("api")).unwrap();
    fs::write(
        skeleton.join("service.yaml"),
        "name: __SERVICE_NAME__\ndomain: __DOMAIN__\nowner_team: __OWNER_TEAM__\n",
    )
    .unwrap();
    fs::write(
        skeleton.join("api/service.proto"),
        "package __PROTO_PACKAGE__;\nservice __SERVICE_CLASS__Service {}\n",
    )
    .unwrap();

    let output = root.join("services/commerce/order-service");
    create_service_from_template(CreateServiceSpec {
        root,
        template_dir: &template,
        output_dir: &output,
        service_name: "order-service",
        domain: "commerce",
        owner_team: "team-orders",
        framework: "native",
        template_name: "template-go-grpc",
        proto_package: "executesoft.commerce.order.service.v1",
        service_class: "OrderService",
    })
    .unwrap();

    let metadata = fs::read_to_string(output.join("service.yaml")).unwrap();
    assert!(metadata.contains("name: order-service"));
    assert!(metadata.contains("domain: commerce"));
    assert!(metadata.contains("owner_team: team-orders"));

    let shared =
        fs::read_to_string(root.join("contracts/protobuf/commerce-order-service.proto")).unwrap();
    assert!(shared.contains("package executesoft.commerce.order.service.v1;"));
    assert!(shared.contains("service OrderServiceService {}"));
}
