defmodule BDS.ScriptsTest do
  use ExUnit.Case, async: false

  alias BDS.Repo
  alias BDS.Scripts.Script

  setup do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
    temp_dir = Path.join(System.tmp_dir!(), "bds-scripts-#{System.unique_integer([:positive])}")
    File.mkdir_p!(temp_dir)
    on_exit(fn -> File.rm_rf(temp_dir) end)

    {:ok, project} = BDS.Projects.create_project(%{name: "Scripts", data_path: temp_dir})
    %{project: project, temp_dir: temp_dir}
  end

  test "create_script creates draft scripts with default entrypoints by kind", %{project: project} do
    assert {:ok, utility} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Importer",
               kind: :utility,
               content: "function main() end"
             })

    assert utility.slug == "importer"
    assert utility.entrypoint == "main"
    assert utility.status == :draft
    assert utility.enabled == true
    assert utility.version == 1
    assert utility.file_path == ""

    assert {:ok, macro_script} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Render Card",
               kind: :macro,
               content: "function render() end"
             })

    assert macro_script.entrypoint == "render"
    assert macro_script.slug == "render-card"
  end

  test "publish_script writes a lua file with frontmatter and clears draft content", %{
    project: project,
    temp_dir: temp_dir
  } do
    assert {:ok, script} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Process Feed",
               kind: :utility,
               content: "function main() return 'ok' end"
             })

    assert {:ok, published} = BDS.Scripts.publish_script(script.id)

    assert published.status == :published
    assert published.content == nil
    assert published.file_path == "scripts/process-feed.lua"

    full_path = Path.join(temp_dir, published.file_path)
    assert File.exists?(full_path)

    contents = File.read!(full_path)
    assert contents =~ "---\nid: #{published.id}\n"
    assert contents =~ "projectId: #{project.id}\n"
    assert contents =~ "slug: process-feed\n"
    assert contents =~ "title: Process Feed\n"
    assert contents =~ "kind: utility\n"
    assert contents =~ "entrypoint: main\n"
    assert contents =~ "enabled: true\n"
    assert contents =~ "version: 1\n"
    assert contents =~ ~r/createdAt: '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z'\n/
    assert contents =~ ~r/updatedAt: '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z'\n/
    assert contents =~ "\n---\nfunction main() return 'ok' end\n"
    refute File.exists?(full_path <> ".tmp")
  end

  test "update_script bumps version and reopens a published script when content changes", %{
    project: project
  } do
    assert {:ok, script} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Render Card",
               kind: :macro,
               content: "function render() return 'v1' end"
             })

    assert {:ok, published} = BDS.Scripts.publish_script(script.id)
    assert published.status == :published

    assert {:ok, updated} =
             BDS.Scripts.update_script(script.id, %{
               content: "function render() return 'v2' end",
               enabled: false
             })

    assert updated.version == 2
    assert updated.status == :draft
    assert updated.enabled == false
    assert updated.file_path == "scripts/render-card.lua"
    assert updated.content == "function render() return 'v2' end"
    assert updated.updated_at >= published.updated_at
  end

  test "delete_script removes the published file and database row", %{
    project: project,
    temp_dir: temp_dir
  } do
    assert {:ok, script} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Cleanup",
               kind: :utility,
               content: "function main() return true end"
             })

    assert {:ok, published} = BDS.Scripts.publish_script(script.id)
    assert File.exists?(Path.join(temp_dir, published.file_path))

    assert {:ok, :deleted} = BDS.Scripts.delete_script(published.id)
    assert Repo.get(Script, published.id) == nil
    refute File.exists?(Path.join(temp_dir, published.file_path))
  end

  test "publish_script rejects invalid Lua syntax", %{project: project} do
    assert {:ok, script} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Bad Script",
               kind: :utility,
               content: "function main( missing end"
             })

    assert {:error, {:invalid_script, _reason}} = BDS.Scripts.publish_script(script.id)

    reloaded = Repo.get!(Script, script.id)
    assert reloaded.status == :draft
    assert reloaded.content == "function main( missing end"
  end

  test "publish_script allows valid Lua syntax", %{project: project} do
    assert {:ok, script} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Good Script",
               kind: :utility,
               content: "function main() return 42 end"
             })

    assert {:ok, published} = BDS.Scripts.publish_script(script.id)
    assert published.status == :published
  end

  test "create_and_publish_script creates a published script with file in one step", %{
    project: project,
    temp_dir: temp_dir
  } do
    assert {:ok, script} =
             BDS.Scripts.create_and_publish_script(%{
               project_id: project.id,
               title: "Published Utility",
               kind: :utility,
               content: "function main() return 'ok' end"
             })

    assert script.status == :published
    assert script.content == nil
    assert script.slug == "published-utility"
    assert script.enabled == true
    assert script.version == 1
    assert script.entrypoint == "main"
    assert script.file_path == "scripts/published-utility.lua"

    full_path = Path.join(temp_dir, script.file_path)
    assert File.exists?(full_path)

    contents = File.read!(full_path)
    assert contents =~ "---\nid: #{script.id}\n"
    assert contents =~ "projectId: #{project.id}\n"
    assert contents =~ "slug: published-utility\n"
    assert contents =~ "title: Published Utility\n"
    assert contents =~ "kind: utility\n"
    assert contents =~ "entrypoint: main\n"
    assert contents =~ "enabled: true\n"
    assert contents =~ "version: 1\n"
    assert contents =~ ~r/createdAt: '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z'\n/
    assert contents =~ ~r/updatedAt: '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z'\n/
    assert contents =~ "\n---\nfunction main() return 'ok' end\n"
  end

  test "create_and_publish_script uses macro default entrypoint", %{
    project: project
  } do
    assert {:ok, script} =
             BDS.Scripts.create_and_publish_script(%{
               project_id: project.id,
               title: "Render Helper",
               kind: :macro,
               content: "function render() end"
             })

    assert script.entrypoint == "render"
    assert script.slug == "render-helper"
  end

  test "create_and_publish_script rejects invalid Lua syntax", %{project: project} do
    assert {:error, {:invalid_script, _reason}} =
             BDS.Scripts.create_and_publish_script(%{
               project_id: project.id,
               title: "Bad Script",
               kind: :utility,
               content: "function main( missing end"
             })
  end

  test "create_and_publish_script deduplicates slug on title conflict", %{project: project} do
    assert {:ok, _first} =
             BDS.Scripts.create_and_publish_script(%{
               project_id: project.id,
               title: "Same Title",
               kind: :utility,
               content: "function main() return 'v1' end"
             })

    assert {:ok, second} =
             BDS.Scripts.create_and_publish_script(%{
               project_id: project.id,
               title: "Same Title",
               kind: :utility,
               content: "function main() return 'v2' end"
             })

    assert second.slug == "same-title-2"
    assert second.status == :published
  end

  test "create_script deduplicates slug on title conflict (UniqueScriptSlug)", %{
    project: project
  } do
    assert {:ok, first} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Dup Slug",
               kind: :utility,
               content: "function main() return 'first' end"
             })

    assert first.slug == "dup-slug"

    assert {:ok, second} =
             BDS.Scripts.create_script(%{
               project_id: project.id,
               title: "Dup Slug",
               kind: :utility,
               content: "function main() return 'second' end"
             })

    assert second.slug == "dup-slug-2"
    refute second.id == first.id
  end

  test "rebuild_scripts_from_files recreates published scripts from disk", %{
    project: project,
    temp_dir: temp_dir
  } do
    script_dir = Path.join(temp_dir, "scripts")
    File.mkdir_p!(script_dir)

    file_path = Path.join(script_dir, "recovered.lua")

    File.write!(
      file_path,
      [
        "---",
        "id: script-from-file",
        "projectId: #{project.id}",
        "slug: recovered",
        "title: Recovered Script",
        "kind: utility",
        "entrypoint: main",
        "enabled: true",
        "version: 4",
        "createdAt: 1970-01-01T00:00:00.301Z",
        "updatedAt: 1970-01-01T00:00:00.404Z",
        "---",
        "function main() return 'restored' end",
        ""
      ]
      |> Enum.join("\n")
    )

    assert {:ok, scripts} = BDS.Scripts.rebuild_scripts_from_files(project.id)
    assert length(scripts) == 1

    [script] = Repo.all(Script)
    assert script.id == "script-from-file"
    assert script.slug == "recovered"
    assert script.title == "Recovered Script"
    assert script.kind == :utility
    assert script.entrypoint == "main"
    assert script.enabled == true
    assert script.version == 4
    assert script.status == :published
    assert script.file_path == "scripts/recovered.lua"
    assert script.content == nil
    assert script.created_at == 301
    assert script.updated_at == 404
  end
end
