// Copyright 2025 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::common::CommandOutput;
use crate::common::TestEnvironment;
use crate::common::TestWorkDir;

fn create_test_environment() -> TestEnvironment {
    let mut test_env = TestEnvironment::default();
    test_env.add_env_var("JJ_RANDOMNESS_SEED", "0");
    test_env.add_env_var("JJ_TIMESTAMP", "2001-01-01T00:00:00+00:00");
    test_env.add_config("experimental.record-predecessors-in-commit = false");
    test_env
}

#[test]
fn test_identical_commits() {
    let test_env = create_test_environment();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    work_dir.run_jj(["new", "root()", "-m=test"]).success();
    // TODO: Should not fail
    insta::assert_snapshot!(work_dir.run_jj(["new", "root()", "-m=test"]), @"
    ------- stderr -------
    Internal error: Unexpected error from backend
    Caused by: Newly-created commit e94ed463cbb0776612e308eba2ecaae74a7f8a73 already exists
    [EOF]
    [exit status: 255]
    ");
    // There should be a single "test" commit
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  e94ed463cbb0 test
    ◆  000000000000
    [EOF]
    ");
}

/// Create "test1" commit, then rewrite it in the same way "concurrently" (by
/// starting at the same operation)
#[test]
fn test_identical_commits_concurrently() {
    let test_env = create_test_environment();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    work_dir.run_jj(["new", "root()", "-m=test1"]).success();
    work_dir.run_jj(["describe", "-m=test2"]).success();
    work_dir
        .run_jj(["describe", "-m=test2", "--at-op=@-"])
        .success();
    // There should be a single "test2" commit
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  c5abd2256ac0 test2
    ◆  000000000000
    [EOF]
    ------- stderr -------
    Concurrent modification detected, resolving automatically.
    [EOF]
    ");
}

/// Create commit "test1", then rewrite it to "test2", then rewrite it back to
/// "test1"
#[test]
fn test_identical_commits_by_cycling_rewrite() {
    let test_env = create_test_environment();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    work_dir.run_jj(["new", "root()", "-m=test1"]).success();
    work_dir.run_jj(["describe", "-m=test2"]).success();
    // TODO: Should not fail
    insta::assert_snapshot!(work_dir.run_jj(["describe", "-m=test1"]), @"
    ------- stderr -------
    Internal error: Unexpected error from backend
    Caused by: Newly-created commit 053222c21fa06b9492e22346f8f70e732231ad4f already exists
    [EOF]
    [exit status: 255]
    ");
    insta::assert_snapshot!(work_dir.run_jj(["evolog"]), @"
    @  oxmtprsl test.user@example.com 2001-01-01 11:00:00 c5abd225
    │  (empty) test2
    │  -- operation f0e7f7b1629b describe commit 053222c21fa06b9492e22346f8f70e732231ad4f
    ○  oxmtprsl/1 test.user@example.com 2001-01-01 11:00:00 053222c2 (hidden)
       (empty) test1
       -- operation 72d9ec4ca389 new empty commit
    [EOF]
    ");
    // TODO: Test `jj op diff --from @--`
}

/// Create commits "test1" and "test2" and rewrite "test1". Then rewrite "test2"
/// to become identical to the rewritten "test1".
#[test]
fn test_identical_commits_by_convergent_rewrite() {
    let test_env = create_test_environment();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    work_dir.run_jj(["new", "root()", "-m=test1"]).success();
    work_dir.run_jj(["new", "root()", "-m=test2"]).success();
    work_dir
        .run_jj(["describe", "-m=test3", "subject(test1)"])
        .success();
    // TODO: Should not fail
    insta::assert_snapshot!(work_dir.run_jj(["describe", "-m=test3", "subject(test2)"]), @"
    ------- stderr -------
    Internal error: Unexpected error from backend
    Caused by: Newly-created commit 460733f1f6f9283d5a810b231dd3f846fd3a6f04 already exists
    [EOF]
    [exit status: 255]
    ");
    // TODO: The "test3" commit should have either "test1" or "test2" as predecessor
    // (or both?)
    insta::assert_snapshot!(work_dir.run_jj(["evolog"]), @"
    @  oxmtprsl/1 test.user@example.com 2001-01-01 11:00:00 c5abd225 (divergent)
       (empty) test2
       -- operation 2c0ac8f6dfb7 new empty commit
    [EOF]
    ");
}

/// Create commits "test1" and "test2" and then rewrite both of them to be
/// identical in a single operation
#[test]
fn test_identical_commits_by_convergent_rewrite_one_operation() {
    let test_env = create_test_environment();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    work_dir.run_jj(["new", "root()", "-m=test1"]).success();
    work_dir.run_jj(["new", "root()", "-m=test2"]).success();
    // TODO: Should not fail
    insta::assert_snapshot!(work_dir.run_jj(["describe", "-m=test3", "root()+"]), @"
    ------- stderr -------
    Internal error: Unexpected error from backend
    Caused by: Newly-created commit 460733f1f6f9283d5a810b231dd3f846fd3a6f04 already exists
    [EOF]
    [exit status: 255]
    ");
    // TODO: There should be a single "test3" commit
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  c5abd2256ac0 test2
    │ ○  053222c21fa0 test1
    ├─╯
    ◆  000000000000
    [EOF]
    ");
    // TODO: The "test3" commit should have either "test1" or "test2" as predecessor
    // (or both?)
    insta::assert_snapshot!(work_dir.run_jj(["evolog"]), @"
    @  oxmtprsl/0 test.user@example.com 2001-01-01 11:00:00 c5abd225 (divergent)
       (empty) test2
       -- operation 2c0ac8f6dfb7 new empty commit
    [EOF]
    ");
}

/// Create two stacked commits. Then reorder them so they become rewrites of
/// each other.
#[test]
fn test_identical_commits_swap_by_reordering() {
    let test_env = create_test_environment();
    test_env.run_jj_in(".", ["git", "init", "repo"]).success();
    let work_dir = test_env.work_dir("repo");

    work_dir.run_jj(["new", "root()", "-m=test"]).success();
    work_dir.run_jj(["new", "-m=test"]).success();
    // Test the setup
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  5bae90c9b34d test
    ○  e94ed463cbb0 test
    ◆  000000000000
    [EOF]
    ");
    // TODO: Should not fail
    insta::assert_snapshot!(work_dir.run_jj(["rebase", "-r=@", "-B=@-"]), @"
    ------- stderr -------
    Internal error: Unexpected error from backend
    Caused by: Newly-created commit e94ed463cbb0776612e308eba2ecaae74a7f8a73 already exists
    [EOF]
    [exit status: 255]
    ");
    // There same two commits should still be visible
    insta::assert_snapshot!(get_log_output(&work_dir), @"
    @  5bae90c9b34d test
    ○  e94ed463cbb0 test
    ◆  000000000000
    [EOF]
    ");
    // TODO: Each commit should be a predecessor of the other
    insta::assert_snapshot!(work_dir.run_jj(["evolog", "-r=@"]), @"
    @  oxmtprsl/0 test.user@example.com 2001-01-01 11:00:00 5bae90c9 (divergent)
       (empty) test
       -- operation 51fb079a7e7c new empty commit
    [EOF]
    ");
    insta::assert_snapshot!(work_dir.run_jj(["evolog", "-r=@-"]), @"
    ○  oxmtprsl/1 test.user@example.com 2001-01-01 11:00:00 e94ed463 (divergent)
       (empty) test
       -- operation 03fecb732164 new empty commit
    [EOF]
    ");
    // TODO: Test that `jj op show` displays something reasonable
}

#[must_use]
fn get_log_output(work_dir: &TestWorkDir) -> CommandOutput {
    let template = r#"commit_id.short() ++ " " ++ description"#;
    work_dir.run_jj(["log", "-T", template])
}
