defmodule Tarakan.RepositoryCodeHostedTest do
  use Tarakan.DataCase, async: false

  import Tarakan.AccountsFixtures

  alias Tarakan.Accounts
  alias Tarakan.HostedRepositories
  alias Tarakan.HostedRepositories.Storage
  alias Tarakan.RepositoryCode
  alias Tarakan.RepositoryCode.Commit
  alias Tarakan.RepositoryCode.File, as: CodeFile
  alias Tarakan.RepositoryCode.Tree

  setup do
    on_exit(fn ->
      File.rm_rf(Application.fetch_env!(:tarakan, Tarakan.HostedRepositories)[:root])
    end)

    Tarakan.RepositoryCode.Cache.clear()

    account = account_fixture()
    scope = Accounts.scope_for_account(account)
    {:ok, repository} = HostedRepositories.create(scope, %{"name" => "browsable"})
    %{repository: repository}
  end

  defp push_fixture_commit(repository, tmp_dir) do
    work = Path.join(tmp_dir, "work")

    {_output, 0} =
      System.cmd("git", ["clone", "--quiet", Storage.dir(repository), work],
        stderr_to_stdout: true
      )

    File.write!(Path.join(work, "README.md"), "# Hosted\n")
    File.mkdir_p!(Path.join(work, "lib"))
    File.write!(Path.join(work, "lib/code.ex"), "IO.puts(:ok)\n")

    env = [
      {"GIT_AUTHOR_NAME", "t"},
      {"GIT_AUTHOR_EMAIL", "t@example.com"},
      {"GIT_COMMITTER_NAME", "t"},
      {"GIT_COMMITTER_EMAIL", "t@example.com"}
    ]

    {_output, 0} = System.cmd("git", ["-C", work, "add", "."], env: env)
    {_output, 0} = System.cmd("git", ["-C", work, "commit", "--quiet", "-m", "seed"], env: env)

    {_output, 0} =
      System.cmd("git", ["-C", work, "push", "--quiet", "origin", "HEAD"],
        env: env,
        stderr_to_stdout: true
      )

    {sha, 0} = System.cmd("git", ["-C", work, "rev-parse", "HEAD"])
    String.trim(sha)
  end

  defp push_second_commit(repository, tmp_dir) do
    work = Path.join(tmp_dir, "work-second")

    {_output, 0} =
      System.cmd("git", ["clone", "--quiet", Storage.dir(repository), work],
        stderr_to_stdout: true
      )

    File.write!(Path.join(work, "CHANGELOG.md"), "# Changes\n")

    env = [
      {"GIT_AUTHOR_NAME", "t"},
      {"GIT_AUTHOR_EMAIL", "t@example.com"},
      {"GIT_COMMITTER_NAME", "t"},
      {"GIT_COMMITTER_EMAIL", "t@example.com"}
    ]

    {_output, 0} = System.cmd("git", ["-C", work, "add", "."], env: env)
    {_output, 0} = System.cmd("git", ["-C", work, "commit", "--quiet", "-m", "second"], env: env)

    {_output, 0} =
      System.cmd("git", ["-C", work, "push", "--quiet", "origin", "HEAD"],
        env: env,
        stderr_to_stdout: true
      )

    {sha, 0} = System.cmd("git", ["-C", work, "rev-parse", "HEAD"])
    String.trim(sha)
  end

  defp push_branch_commit(repository, tmp_dir, branch, message) do
    work = Path.join(tmp_dir, "work-#{branch}")

    {_output, 0} =
      System.cmd("git", ["clone", "--quiet", Storage.dir(repository), work],
        stderr_to_stdout: true
      )

    File.write!(Path.join(work, "FEATURE.md"), "# #{message}\n")

    env = [
      {"GIT_AUTHOR_NAME", "t"},
      {"GIT_AUTHOR_EMAIL", "t@example.com"},
      {"GIT_COMMITTER_NAME", "t"},
      {"GIT_COMMITTER_EMAIL", "t@example.com"}
    ]

    {_output, 0} = System.cmd("git", ["-C", work, "checkout", "--quiet", "-b", branch], env: env)
    {_output, 0} = System.cmd("git", ["-C", work, "add", "."], env: env)
    {_output, 0} = System.cmd("git", ["-C", work, "commit", "--quiet", "-m", message], env: env)

    {_output, 0} =
      System.cmd("git", ["-C", work, "push", "--quiet", "origin", branch],
        env: env,
        stderr_to_stdout: true
      )

    {sha, 0} = System.cmd("git", ["-C", work, "rev-parse", "HEAD"])
    String.trim(sha)
  end

  @tag :tmp_dir
  test "browses a hosted repository from local storage", %{
    repository: repository,
    tmp_dir: tmp_dir
  } do
    sha = push_fixture_commit(repository, tmp_dir)

    assert {:ok, ^sha} = RepositoryCode.resolve_default_commit(repository)

    assert {:ok, %Tree{} = tree} = RepositoryCode.browse(repository, sha, nil)
    paths = Enum.map(tree.entries, & &1.path)
    assert "README.md" in paths
    assert "lib" in paths

    assert {:ok, %CodeFile{} = file} = RepositoryCode.browse(repository, sha, "README.md")
    assert file.content == "# Hosted\n"

    assert {:ok, %Tree{} = subtree} = RepositoryCode.browse(repository, sha, "lib")
    assert Enum.map(subtree.entries, & &1.path) == ["lib/code.ex"]

    assert {:error, :not_found} =
             RepositoryCode.browse(repository, sha, "missing.txt")
  end

  test "an empty hosted repository reports empty_repository", %{repository: repository} do
    assert {:error, :empty_repository} = RepositoryCode.resolve_default_commit(repository)
  end

  test "unknown commits in a hosted repository are not found", %{repository: repository} do
    unknown = String.duplicate("b", 40)
    assert {:error, :not_found} = RepositoryCode.browse(repository, unknown, nil)
  end

  @tag :tmp_dir
  test "lists commit history newest first", %{repository: repository, tmp_dir: tmp_dir} do
    first_sha = push_fixture_commit(repository, tmp_dir)
    tip = push_second_commit(repository, tmp_dir)

    assert {:ok, %{tip_sha: ^tip, commits: commits}} = RepositoryCode.list_commits(repository)

    assert [
             %Commit{sha: ^tip, subject: "second", author_email: "t@example.com"},
             %Commit{sha: ^first_sha, subject: "seed"}
           ] = commits

    assert {:ok, %{commits: [only]}} = RepositoryCode.list_commits(repository, 1)
    assert only.sha == tip
  end

  test "an empty hosted repository has no history", %{repository: repository} do
    assert {:error, :empty_repository} = RepositoryCode.list_commits(repository)
  end

  @tag :tmp_dir
  test "authorizes historical commits reachable from a branch tip", %{
    repository: repository,
    tmp_dir: tmp_dir
  } do
    first_sha = push_fixture_commit(repository, tmp_dir)
    _tip = push_second_commit(repository, tmp_dir)

    assert :ok = RepositoryCode.authorize_public_commit(repository, first_sha)

    assert {:error, :not_found} =
             RepositoryCode.authorize_public_commit(repository, String.duplicate("c", 40))
  end

  @tag :tmp_dir
  test "shows one commit with full message and patch", %{
    repository: repository,
    tmp_dir: tmp_dir
  } do
    first_sha = push_fixture_commit(repository, tmp_dir)
    tip = push_second_commit(repository, tmp_dir)

    assert {:ok, %Commit{} = commit} = RepositoryCode.show_commit(repository, tip)
    assert commit.sha == tip
    assert commit.subject == "second"
    assert commit.patch =~ "diff --git a/CHANGELOG.md b/CHANGELOG.md"
    assert commit.patch =~ "+# Changes"
    refute commit.patch_truncated

    # Historical (non-tip) commits are reachable, arbitrary SHAs are not.
    assert {:ok, %Commit{sha: ^first_sha}} = RepositoryCode.show_commit(repository, first_sha)

    assert {:error, :not_found} =
             RepositoryCode.show_commit(repository, String.duplicate("e", 40))
  end

  @tag :tmp_dir
  test "lists history for a named branch", %{repository: repository, tmp_dir: tmp_dir} do
    _main_sha = push_fixture_commit(repository, tmp_dir)
    feature_sha = push_branch_commit(repository, tmp_dir, "feature", "feature work")

    assert {:ok, %{commits: main_commits}} = RepositoryCode.list_commits(repository)
    refute Enum.any?(main_commits, &(&1.sha == feature_sha))

    assert {:ok, %{tip_sha: ^feature_sha, commits: feature_commits}} =
             RepositoryCode.list_commits(repository, 50, branch: "feature")

    assert [%Commit{sha: ^feature_sha, subject: "feature work"} | _rest] = feature_commits

    assert {:error, :not_found} =
             RepositoryCode.list_commits(repository, 50, branch: "missing")
  end
end
