defmodule BDS.Desktop.ShellCommandsTest do
  use ExUnit.Case, async: false

  alias BDS.AI
  alias BDS.Desktop.ShellCommands
  alias BDS.Media
  alias BDS.Metadata
  alias BDS.Posts
  alias BDS.Repo

  defmodule FakeRuntime do
    def generate(_endpoint, request, opts) do
      test_pid = Keyword.fetch!(opts, :test_pid)
      send(test_pid, {:runtime_request, request.operation})

      case request.operation do
        :translate_post ->
          {:ok,
           %{
             json: %{
               "title" => "Hallo Welt",
               "excerpt" => "Kurze Zusammenfassung",
               "content" => "# Hallo Welt\n\nUbersetzter Inhalt"
             },
             usage: %{
               input_tokens: 22,
               output_tokens: 14,
               cache_read_tokens: 0,
               cache_write_tokens: 0
             }
           }}

        :translate_media ->
          {:ok,
           %{
             json: %{
               "title" => "Medientitel",
               "alt" => "Medien Alt",
               "caption" => "Medien Beschriftung"
             },
             usage: %{
               input_tokens: 12,
               output_tokens: 10,
               cache_read_tokens: 0,
               cache_write_tokens: 0
             }
           }}
      end
    end
  end

  defmodule SlowEmbeddingBackend do
    @behaviour BDS.Embeddings.Backend

    @impl true
    def model_info do
      BDS.Embeddings.Backends.InApp.model_info()
    end

    @impl true
    def embed(text, opts) do
      Process.sleep(10)
      BDS.Embeddings.Backends.InApp.embed(text, opts)
    end
  end

  defmodule BlockingEmbeddingBackend do
    @behaviour BDS.Embeddings.Backend

    @impl true
    def model_info do
      %{model_id: "blocking/test", dimensions: 384}
    end

    @impl true
    def embed(_text, _opts) do
      if test_pid = Application.get_env(:bds, :embedding_test_pid) do
        send(test_pid, {:embedding_started, self()})
      end

      receive do
        :release_embedding -> {:ok, List.duplicate(0.0, 384)}
      after
        5_000 -> {:ok, List.duplicate(0.0, 384)}
      end
    end
  end

  setup do
    if :ets.whereis(BDS.Preview.ServerTable) != :undefined do
      case :ets.lookup(BDS.Preview.ServerTable, :current) do
        [{:current, %{project_id: project_id}}] ->
          _ = BDS.Preview.stop_preview(project_id)

        _other ->
          :ok
      end
    end

    :ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
    Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, {:shared, self()})
    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.mode(BDS.Repo, :manual) end)
    :ok = Ecto.Adapters.SQL.Sandbox.allow(BDS.Repo, self(), Process.whereis(BDS.Preview))
    :ok = Ecto.Adapters.SQL.Sandbox.allow(BDS.Repo, self(), Process.whereis(BDS.Publishing))
    :ok = BDS.Tasks.clear_finished()

    temp_dir =
      Path.join(System.tmp_dir!(), "bds-shell-commands-#{System.unique_integer([:positive])}")

    File.mkdir_p!(temp_dir)

    on_exit(fn ->
      File.rm_rf(temp_dir)
      _ = BDS.Preview.stop_preview("default")
      _ = BDS.Tasks.clear_finished()
    end)

    {:ok, project} = BDS.Projects.create_project(%{name: "Shell Commands", data_path: temp_dir})
    {:ok, project} = BDS.Projects.set_active_project(project.id)

    %{project: project, temp_dir: temp_dir}
  end

  test "open_in_browser starts preview for the active project and returns a preview url", %{
    project: project
  } do
    assert {:ok, result} = ShellCommands.execute("open_in_browser")

    assert result.kind == "open_url"
    assert result.action == "open_in_browser"
    assert result.url == BDS.Preview.base_url() <> "/"
    assert result.project_id == project.id
  end

  test "validate_translations returns an editor payload with current translation gaps", %{
    project: project,
    temp_dir: temp_dir
  } do
    assert {:ok, post} =
             BDS.Posts.create_post(%{
               project_id: project.id,
               title: "Hello",
               content: "World",
               language: "en"
             })

    assert {:ok, published_post} = BDS.Posts.publish_post(post.id)

    assert {:ok, translation} =
             BDS.Posts.upsert_post_translation(post.id, "de", %{
               title: "Hallo",
               content: "Welt",
               status: :published
             })

    translation_id = translation.id

    invalid_file_path =
      Path.join([
        temp_dir,
        Path.dirname(published_post.file_path),
        "#{published_post.slug}.en.md"
      ])

    File.write!(
      invalid_file_path,
      [
        "---",
        "translationFor: #{post.id}",
        "language: en",
        "title: Wrong Language",
        "---",
        "Invalid translation",
        ""
      ]
      |> Enum.join("\n")
    )

    assert {:ok, result} = ShellCommands.execute("validate_translations")

    assert result.kind == "task_queued"
    assert result.action == "validate_translations"
    assert is_binary(result.task_id)

    completed = wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)))

    assert completed.group_name == "Validation"
    assert completed.result.kind == "open_editor"
    assert completed.result.route == "translation_validation"
    assert completed.result.payload.checked_database_row_count == 1
    assert completed.result.payload.checked_filesystem_file_count == 1

    post_id = post.id

    assert [
             %{
               "issue" => "content-in-database",
               "translation_for" => ^post_id,
               "translation_id" => ^translation_id,
               "translation_language" => "de"
             }
           ] = completed.result.payload.invalid_database_rows

    assert [
             %{
               "issue" => "same-language-as-canonical",
               "translation_for" => ^post_id,
               "translation_language" => "en",
               "file_path" => ^invalid_file_path
             }
           ] = completed.result.payload.invalid_filesystem_files
  end

  test "fill_missing_translations queues a tracked AI task and publishes missing post and media translations",
       %{project: project, temp_dir: temp_dir} do
    assert {:ok, post} =
             Posts.create_post(%{
               project_id: project.id,
               title: "Hello",
               excerpt: "English summary",
               content: "World body",
               language: "en"
             })

    media_source = Path.join(temp_dir, "source-image.txt")
    File.write!(media_source, "image bytes")

    assert {:ok, media} =
             Media.import_media(%{
               project_id: project.id,
               source_path: media_source,
               title: "Image title",
               alt: "Image alt",
               caption: "Image caption",
               language: "en"
             })

    assert {:ok, _link} = Media.link_media_to_post(media.id, post.id)
    assert {:ok, _published_post} = Posts.publish_post(post.id)

    configure_auto_translation_test_runtime()

    assert {:ok, _metadata} =
             Metadata.update_project_metadata(project.id, %{
               main_language: "en",
               blog_languages: ["en", "de"]
             })

    assert {:ok, result} = ShellCommands.execute("fill_missing_translations")

    assert result.kind == "task_queued"
    assert result.action == "fill_missing_translations"
    assert is_binary(result.task_id)

    completed =
      wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)), 5_000)

    assert completed.group_name == "AI"
    assert completed.result.project_id == project.id
    assert completed.result.translated_posts == 1
    assert completed.result.translated_media == 1
    assert completed.result.failed_count == 0

    translation = Repo.get_by!(BDS.Posts.Translation, translation_for: post.id, language: "de")
    assert translation.status == :published
    assert translation.content == nil
    assert is_binary(translation.file_path)
    assert File.exists?(Path.join(temp_dir, translation.file_path))

    media_translation =
      Repo.get_by!(BDS.Media.Translation, translation_for: media.id, language: "de")

    assert media_translation.title == "Medientitel"
    assert media_translation.alt == "Medien Alt"
    assert media_translation.caption == "Medien Beschriftung"
    assert File.exists?(Path.join(temp_dir, media.file_path <> ".de.meta"))

    assert_received {:runtime_request, :translate_post}
    assert_received {:runtime_request, :translate_media}
  end

  test "fill_missing_translations returns a no-op output when only one language is configured",
       %{project: project} do
    assert {:ok, _metadata} =
             Metadata.update_project_metadata(project.id, %{
               main_language: "en",
               blog_languages: ["en"]
             })

    assert {:ok, result} = ShellCommands.execute("fill_missing_translations")

    assert result.kind == "output"
    assert result.action == "fill_missing_translations"
    assert result.message == "All translations are up to date"
    assert BDS.Tasks.list_tasks() == []
  end

  test "fill_missing_translations uses the media canonical language when choosing missing media targets",
       %{project: project, temp_dir: temp_dir} do
    assert {:ok, post} =
             Posts.create_post(%{
               project_id: project.id,
               title: "Hallo Welt",
               excerpt: "Deutsche Zusammenfassung",
               content: "Deutscher Inhalt",
               language: "de"
             })

    media_source = Path.join(temp_dir, "english-media.txt")
    File.write!(media_source, "image bytes")

    assert {:ok, media} =
             Media.import_media(%{
               project_id: project.id,
               source_path: media_source,
               title: "English image",
               alt: "English alt",
               caption: "English caption",
               language: "en"
             })

    assert {:ok, _link} = Media.link_media_to_post(media.id, post.id)
    assert {:ok, _published_post} = Posts.publish_post(post.id)

    configure_auto_translation_test_runtime()

    assert {:ok, _metadata} =
             Metadata.update_project_metadata(project.id, %{
               main_language: "de",
               blog_languages: ["de", "en"]
             })

    assert {:ok, result} = ShellCommands.execute("fill_missing_translations")

    completed =
      wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)), 5_000)

    assert completed.result.translated_posts == 1
    assert completed.result.translated_media == 1
    assert Repo.get_by(BDS.Media.Translation, translation_for: media.id, language: "en") == nil

    media_translation =
      Repo.get_by!(BDS.Media.Translation, translation_for: media.id, language: "de")

    assert media_translation.title == "Medientitel"
  end

  test "validate_site queues a tracked validation task and returns the report as an editor payload" do
    assert {:ok, result} = ShellCommands.execute("validate_site")

    assert result.kind == "task_queued"
    assert result.action == "validate_site"
    assert is_binary(result.task_id)

    completed = wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)))

    assert completed.group_name == "Validation"
    assert is_binary(completed.message)
    assert String.starts_with?(completed.message, "Validation complete (")
    assert completed.result.kind == "open_editor"
    assert completed.result.route == "site_validation"
    assert is_map(completed.result.payload.summary)
    assert Map.has_key?(completed.result.payload, :missing_url_paths)
    assert Map.has_key?(completed.result.payload, :extra_url_paths)
    assert Map.has_key?(completed.result.payload, :updated_post_url_paths)
    assert Map.has_key?(completed.result.payload.summary, :expected_count)
    assert Map.has_key?(completed.result.payload.summary, :existing_count)
    assert Map.has_key?(completed.result.payload.summary, :updated_count)
  end

  test "metadata_diff queues a tracked maintenance task and returns the report as an editor payload" do
    assert {:ok, result} = ShellCommands.execute("metadata_diff")

    assert result.kind == "task_queued"
    assert result.action == "metadata_diff"
    assert is_binary(result.task_id)

    completed = wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)))

    assert completed.group_name == "Maintenance"
    assert completed.result.kind == "open_editor"
    assert completed.result.route == "metadata_diff"
    assert is_map(completed.result.payload.summary)
  end

  test "rebuild_posts_from_files rebuilds embeddings for published posts when semantic similarity is enabled",
       %{project: project} do
    assert {:ok, _metadata} =
             BDS.Metadata.update_project_metadata(project.id, %{semantic_similarity_enabled: true})

    assert {:ok, post} =
             BDS.Posts.create_post(%{
               project_id: project.id,
               title: "Filesystem Embedding Source",
               content: "space rocket orbit mission galaxy",
               language: "en"
             })

    assert {:ok, published_post} = BDS.Posts.publish_post(post.id)

    assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) !=
             nil

    BDS.Repo.delete_all(BDS.Embeddings.Key)

    assert {:ok, result} = ShellCommands.execute("rebuild_posts_from_files")
    completed = wait_for_task(result.task_id, &(&1.status == :completed))

    assert completed.group_name == "Maintenance"

    assert BDS.Repo.get_by(BDS.Embeddings.Key, project_id: project.id, post_id: published_post.id) !=
             nil
  end

  test "repair_metadata_diff exposes live in-task progress from the repair worker", %{
    project: project
  } do
    original = Application.get_env(:bds, :tasks, [])

    Application.put_env(
      :bds,
      :tasks,
      original
      |> Keyword.put(:max_concurrent, 1)
      |> Keyword.put(:progress_throttle_ms, 0)
    )

    on_exit(fn -> Application.put_env(:bds, :tasks, original) end)

    items =
      Enum.map(1..40, fn _index ->
        %{"entity_type" => "project", "entity_id" => project.id}
      end)

    assert {:ok, result} =
             ShellCommands.execute("repair_metadata_diff", %{
               "direction" => "file_to_db",
               "items" => items
             })

    progressed =
      wait_for_task(
        result.task_id,
        &(&1.status == :running and is_number(&1.progress) and &1.progress > 0.2 and
            &1.progress < 1.0),
        5_000
      )

    assert progressed.group_name == "Maintenance"
    assert String.contains?(progressed.message, "Repairing")
    assert String.contains?(progressed.message, "/")

    assert wait_for_task(result.task_id, &(&1.status == :completed and &1.progress == 1.0), 5_000).status ==
             :completed
  end

  test "import_metadata_diff_orphans imports paired post translations before any embedding work can stall the task",
       %{project: project, temp_dir: temp_dir} do
    original_embeddings = Application.get_env(:bds, :embeddings)
    original_tasks = Application.get_env(:bds, :tasks, [])
    original_test_pid = Application.get_env(:bds, :embedding_test_pid)

    Application.put_env(:bds, :embeddings, backend: BlockingEmbeddingBackend)
    Application.put_env(:bds, :embedding_test_pid, self())
    Application.put_env(:bds, :tasks, Keyword.put(original_tasks, :progress_throttle_ms, 0))

    on_exit(fn ->
      release_blocking_embeddings()
      Enum.each(BDS.Tasks.list_running_tasks(), &BDS.Tasks.cancel_task(&1.id))
      _ = BDS.Tasks.clear_finished()

      if original_embeddings == nil do
        Application.delete_env(:bds, :embeddings)
      else
        Application.put_env(:bds, :embeddings, original_embeddings)
      end

      if original_test_pid == nil do
        Application.delete_env(:bds, :embedding_test_pid)
      else
        Application.put_env(:bds, :embedding_test_pid, original_test_pid)
      end

      Application.put_env(:bds, :tasks, original_tasks)
    end)

    assert {:ok, _metadata} =
             BDS.Metadata.update_project_metadata(project.id, %{semantic_similarity_enabled: true})

    post_orphan_path = "posts/2026/04/shell-orphan-post.md"
    post_translation_orphan_path = "posts/2026/04/shell-orphan-post.es.md"

    File.mkdir_p!(Path.join([temp_dir, "posts", "2026", "04"]))

    File.write!(
      Path.join(temp_dir, post_orphan_path),
      [
        "---",
        "id: shell-orphan-post",
        "title: Shell Orphan Post",
        "slug: shell-orphan-post",
        "status: published",
        "createdAt: 1",
        "updatedAt: 1",
        "publishedAt: 1",
        "tags:",
        "categories:",
        "---",
        "Orphan shell body",
        ""
      ]
      |> Enum.join("\n")
    )

    File.write!(
      Path.join(temp_dir, post_translation_orphan_path),
      [
        "---",
        "id: shell-orphan-post-es",
        "translationFor: shell-orphan-post",
        "language: es",
        "title: Traduccion huerfana",
        "excerpt: Resumen huerfano",
        "status: published",
        "createdAt: 1",
        "updatedAt: 1",
        "publishedAt: 1",
        "---",
        "Contenido huerfano",
        ""
      ]
      |> Enum.join("\n")
    )

    assert {:ok, result} =
             ShellCommands.execute("import_metadata_diff_orphans", %{
               "orphans" => [
                 %{"file_path" => post_orphan_path},
                 %{"file_path" => post_translation_orphan_path}
               ]
             })

    progressed =
      wait_for_task(
        result.task_id,
        fn task ->
          task.status in [:running, :completed] and is_number(task.progress) and
            task.progress > 0.2 and
            Repo.get_by(BDS.Posts.Post, project_id: project.id, file_path: post_orphan_path) !=
              nil and
            Repo.get_by(BDS.Posts.Translation,
              project_id: project.id,
              file_path: post_translation_orphan_path
            ) != nil
        end,
        1_000
      )

    assert progressed.status in [:running, :completed]
    assert is_number(progressed.progress)
    assert progressed.progress > 0.2

    assert Repo.get_by(BDS.Posts.Post, project_id: project.id, file_path: post_orphan_path)

    assert Repo.get_by(BDS.Posts.Translation,
             project_id: project.id,
             file_path: post_translation_orphan_path
           )

    release_blocking_embeddings()

    assert wait_for_task(result.task_id, &(&1.status == :completed and &1.progress == 1.0), 5_000).status ==
             :completed
  end

  test "find_duplicates queues a tracked embeddings task and returns the report as an editor payload" do
    assert {:ok, result} = ShellCommands.execute("find_duplicates")

    assert result.kind == "task_queued"
    assert result.action == "find_duplicates"
    assert is_binary(result.task_id)

    completed = wait_for_task(result.task_id, &(&1.status == :completed and is_map(&1.result)))

    assert completed.group_name == "Embeddings"
    assert completed.result.kind == "open_editor"
    assert completed.result.route == "find_duplicates"
    assert is_map(completed.result.payload.summary)
  end

  test "rebuild_embedding_index exposes live in-task progress while rebuilding posts", %{
    project: project
  } do
    original = Application.get_env(:bds, :tasks, [])
    original_embeddings = Application.get_env(:bds, :embeddings)

    Application.put_env(
      :bds,
      :tasks,
      original
      |> Keyword.put(:max_concurrent, 1)
      |> Keyword.put(:progress_throttle_ms, 0)
    )

    Application.put_env(:bds, :embeddings, backend: SlowEmbeddingBackend)

    on_exit(fn ->
      Application.put_env(:bds, :tasks, original)

      if is_nil(original_embeddings) do
        Application.delete_env(:bds, :embeddings)
      else
        Application.put_env(:bds, :embeddings, original_embeddings)
      end
    end)

    assert {:ok, _metadata} =
             BDS.Metadata.update_project_metadata(project.id, %{semantic_similarity_enabled: true})

    Enum.each(1..40, fn index ->
      assert {:ok, post} =
               BDS.Posts.create_post(%{
                 project_id: project.id,
                 title: "Embedding Progress #{index}",
                 content: "space rocket orbit mission galaxy #{index}",
                 language: "en"
               })

      assert {:ok, _published_post} = BDS.Posts.publish_post(post.id)
    end)

    BDS.Repo.delete_all(BDS.Embeddings.Key)

    assert {:ok, result} = ShellCommands.execute("rebuild_embedding_index")

    progressed =
      wait_for_task(
        result.task_id,
        &(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and
            &1.progress < 1.0 and
            is_binary(&1.message) and String.contains?(&1.message, "/")),
        10_000
      )

    assert progressed.group_name == "Embeddings"
    assert String.contains?(progressed.message, "/")

    assert wait_for_task(
             result.task_id,
             &(&1.status == :completed and &1.progress == 1.0),
             10_000
           ).status ==
             :completed
  end

  test "rebuild_database fans out tracked maintenance tasks for the active project" do
    assert {:ok, result} = ShellCommands.execute("rebuild_database")

    assert result.kind == "task_queued"
    assert result.action == "rebuild_database"

    tasks =
      wait_for_tasks_by_name(
        [
          "Rebuild Posts From Files",
          "Rebuild Media From Files",
          "Rebuild Scripts From Files",
          "Rebuild Templates From Files",
          "Rebuild Post Links",
          "Regenerate Missing Thumbnails",
          "Rebuild Embedding Index"
        ],
        &(&1.status == :completed)
      )

    assert Enum.all?(tasks, &(&1.group_name == "Maintenance"))
    assert Enum.all?(tasks, &(&1.status == :completed))
  end

  test "rebuild_database does not run multiple rebuild writers at once", %{temp_dir: temp_dir} do
    original = Application.get_env(:bds, :tasks, [])

    Application.put_env(
      :bds,
      :tasks,
      original
      |> Keyword.put(:max_concurrent, 4)
      |> Keyword.put(:progress_throttle_ms, 0)
    )

    on_exit(fn -> Application.put_env(:bds, :tasks, original) end)

    posts_dir = Path.join([temp_dir, "posts", "2026", "04"])
    media_dir = Path.join([temp_dir, "media", "2026", "04"])
    scripts_dir = Path.join(temp_dir, "scripts")
    templates_dir = Path.join(temp_dir, "templates")

    File.mkdir_p!(posts_dir)
    File.mkdir_p!(media_dir)
    File.mkdir_p!(scripts_dir)
    File.mkdir_p!(templates_dir)

    Enum.each(1..80, fn index ->
      slug = "serial-post-#{index}"

      File.write!(
        Path.join(posts_dir, "#{slug}.md"),
        [
          "---",
          "id: #{slug}",
          "title: Serial Post #{index}",
          "slug: #{slug}",
          "status: published",
          "createdAt: 1711843200",
          "updatedAt: 1711929600",
          "publishedAt: 1712016000",
          "tags:",
          "categories:",
          "---",
          "Body #{index}",
          ""
        ]
        |> Enum.join("\n")
      )

      File.write!(Path.join(media_dir, "asset-#{index}.txt"), "asset #{index}")

      File.write!(
        Path.join(media_dir, "asset-#{index}.txt.meta"),
        [
          "id: serial-media-#{index}",
          "originalName: asset-#{index}.txt",
          "mimeType: text/plain",
          "size: 7",
          "createdAt: 1711843200",
          "updatedAt: 1711929600",
          "tags:",
          ""
        ]
        |> Enum.join("\n")
      )

      File.write!(
        Path.join(scripts_dir, "serial-script-#{index}.lua"),
        [
          "---",
          "id: serial-script-#{index}",
          "slug: serial-script-#{index}",
          "title: Serial Script #{index}",
          "kind: utility",
          "entrypoint: main",
          "enabled: true",
          "version: 1",
          "createdAt: 301",
          "updatedAt: 404",
          "---",
          "function main() return true end",
          ""
        ]
        |> Enum.join("\n")
      )

      File.write!(
        Path.join(templates_dir, "serial-template-#{index}.liquid"),
        [
          "---",
          "id: serial-template-#{index}",
          "slug: serial-template-#{index}",
          "title: Serial Template #{index}",
          "kind: list",
          "enabled: true",
          "version: 1",
          "createdAt: 101",
          "updatedAt: 202",
          "---",
          "<section>Template #{index}</section>",
          ""
        ]
        |> Enum.join("\n")
      )
    end)

    assert {:ok, _result} = ShellCommands.execute("rebuild_database")

    _posts_task =
      wait_for_named_task(
        "Rebuild Posts From Files",
        &(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and
            &1.progress < 1.0),
        10_000
      )

    phase_one_tasks =
      BDS.Tasks.list_tasks()
      |> Enum.filter(
        &(&1.name in [
            "Rebuild Posts From Files",
            "Rebuild Media From Files",
            "Rebuild Scripts From Files",
            "Rebuild Templates From Files"
          ])
      )

    assert Enum.count(phase_one_tasks, &(&1.status == :running)) == 1

    assert Enum.find(phase_one_tasks, &(&1.status == :running)).name == "Rebuild Posts From Files"

    tasks =
      wait_for_tasks_by_name(
        [
          "Rebuild Posts From Files",
          "Rebuild Media From Files",
          "Rebuild Scripts From Files",
          "Rebuild Templates From Files",
          "Rebuild Post Links",
          "Regenerate Missing Thumbnails",
          "Rebuild Embedding Index"
        ],
        &(&1.status == :completed),
        20_000
      )

    assert Enum.all?(tasks, &(&1.status == :completed))
  end

  test "rebuild_database exposes live in-task progress for rebuild work", %{temp_dir: temp_dir} do
    original = Application.get_env(:bds, :tasks, [])

    Application.put_env(
      :bds,
      :tasks,
      original
      |> Keyword.put(:max_concurrent, 1)
      |> Keyword.put(:progress_throttle_ms, 0)
    )

    on_exit(fn -> Application.put_env(:bds, :tasks, original) end)

    posts_dir = Path.join([temp_dir, "posts", "2026", "04"])
    File.mkdir_p!(posts_dir)

    Enum.each(1..80, fn index ->
      slug = "progress-post-#{index}"

      File.write!(
        Path.join(posts_dir, "#{slug}.md"),
        [
          "---",
          "id: #{slug}",
          "title: Progress Post #{index}",
          "slug: #{slug}",
          "status: published",
          "createdAt: 1711843200",
          "updatedAt: 1711929600",
          "publishedAt: 1712016000",
          "tags:",
          "categories:",
          "---",
          "Body #{index}",
          ""
        ]
        |> Enum.join("\n")
      )
    end)

    assert {:ok, result} = ShellCommands.execute("rebuild_database")
    assert result.kind == "task_queued"

    progressed =
      wait_for_named_task(
        "Rebuild Posts From Files",
        &(&1.status == :running and is_number(&1.progress) and &1.progress > 0.0 and
            &1.progress < 1.0),
        5_000
      )

    assert progressed.group_name == "Maintenance"
    assert String.contains?(progressed.message, "post")

    assert wait_for_task(progressed.id, &(&1.status == :completed and &1.progress == 1.0), 5_000).status ==
             :completed

    tasks =
      wait_for_tasks_by_name(
        [
          "Rebuild Posts From Files",
          "Rebuild Media From Files",
          "Rebuild Scripts From Files",
          "Rebuild Templates From Files",
          "Rebuild Post Links",
          "Regenerate Missing Thumbnails",
          "Rebuild Embedding Index"
        ],
        &(&1.status == :completed),
        20_000
      )

    assert Enum.all?(tasks, &(&1.status == :completed))
  end

  test "reindex_text queues a tracked background task for the active project", %{project: project} do
    assert {:ok, result} = ShellCommands.execute("reindex_text")

    assert result.kind == "task_queued"
    assert result.action == "reindex_text"
    assert result.project_id == project.id
    assert is_binary(result.task_id)
    assert is_binary(result.task_group_id)

    tasks =
      wait_for_tasks_by_name(
        ["Reindex Search Text", "Reindex Media Search Text"],
        &(&1.status == :completed)
      )

    assert Enum.all?(tasks, &(&1.group_name == "Search"))
    assert Enum.all?(tasks, &(&1.group_id == result.task_group_id))
    assert Enum.all?(tasks, &(&1.status == :completed))
  end

  test "missing project schema returns a command error instead of raising" do
    BDS.Repo.query!("DROP TABLE projects", [])

    assert {:error, %{message: message}} = ShellCommands.execute("open_in_browser")
    assert message =~ "Project database is not initialized"
  end

  test "rebuild sequencing waits on task messages instead of sleep polling" do
    source = File.read!("lib/bds/desktop/shell_commands.ex")

    func_source =
      Regex.scan(~r/defp wait_for_group_phase(?:_message)?\(.*?(?=\n  defp |\nend)/s, source)
      |> Enum.map(&List.first/1)
      |> Enum.join("\n")

    refute String.contains?(func_source, "Process.sleep"),
           "wait_for_group_phase should not use sleep polling"

    assert String.contains?(func_source, "Phoenix.PubSub.subscribe")
    assert String.contains?(func_source, "receive")
  end

  defp wait_for_task(task_id, matcher, timeout \\ 2_000)

  defp wait_for_task(task_id, _matcher, timeout) when timeout <= 0 do
    BDS.Tasks.get_task(task_id)
  end

  defp wait_for_task(task_id, matcher, timeout) do
    task = BDS.Tasks.get_task(task_id)

    if task && matcher.(task) do
      task
    else
      Process.sleep(50)
      wait_for_task(task_id, matcher, timeout - 50)
    end
  end

  defp wait_for_tasks_by_name(names, matcher), do: wait_for_tasks_by_name(names, matcher, 2_000)

  defp wait_for_tasks_by_name(_names, _matcher, timeout) when timeout <= 0 do
    BDS.Tasks.list_tasks()
  end

  defp wait_for_tasks_by_name(names, matcher, timeout) do
    tasks = BDS.Tasks.list_tasks()
    matching_tasks = Enum.filter(tasks, &(&1.name in names))

    if Enum.all?(names, fn name -> Enum.any?(matching_tasks, &(&1.name == name)) end) and
         Enum.all?(matching_tasks, matcher) do
      matching_tasks
    else
      Process.sleep(50)
      wait_for_tasks_by_name(names, matcher, timeout - 50)
    end
  end

  defp wait_for_named_task(_name, _matcher, timeout) when timeout <= 0 do
    flunk("named task did not reach expected state")
  end

  defp wait_for_named_task(name, matcher, timeout) do
    task = Enum.find(BDS.Tasks.list_tasks(), &(&1.name == name))

    if task && matcher.(task) do
      task
    else
      Process.sleep(20)
      wait_for_named_task(name, matcher, timeout - 20)
    end
  end

  defp release_blocking_embeddings do
    receive do
      {:embedding_started, pid} ->
        send(pid, :release_embedding)
        release_blocking_embeddings()
    after
      0 -> :ok
    end
  end

  defp configure_auto_translation_test_runtime do
    assert {:ok, _endpoint} =
             AI.put_endpoint(:online, %{
               url: "https://api.example.test/v1",
               api_key: "online-secret",
               model: "gpt-4o-mini"
             })

    assert :ok = AI.set_airplane_mode(false)
    assert :ok = AI.put_model_preference(:title, "gpt-4.1-mini")

    Application.put_env(:bds, :posts,
      auto_translation_ai_opts: [
        runtime: FakeRuntime,
        test_pid: self()
      ]
    )
  end
end
