defmodule CodeCorps.TasksTest do
  @moduledoc false

  use CodeCorps.DbAccessCase

  import CodeCorps.GitHub.TestHelpers

  alias CodeCorps.{GithubIssue, Repo, Task, Tasks}

  @base_attrs %{
    "title" => "Test task",
    "markdown" => "A test task",
    "status" => "open"
  }

  @issue_payload load_endpoint_fixture("issue")

  defp valid_attrs() do
    project = insert(:project)
    task_list = insert(:task_list, project: project, inbox: true)
    user = insert(:user)

    @base_attrs
    |> Map.put("project_id", project.id)
    |> Map.put("task_list_id", task_list.id)
    |> Map.put("user_id", user.id)
  end

  describe "create_task/2" do
    test "creates task" do
      {:ok, task} = valid_attrs() |> Tasks.create_task

      assert task.title == @base_attrs["title"]
      assert task.markdown == @base_attrs["markdown"]
      assert task.body
      assert task.status == "open"
      refute task.github_issue_id
      refute task.github_repo_id

      refute_received({:post, "https://api.github.com/" <> _rest, _body, _headers, _options})
    end

    test "sets modified_from to 'code_corps'" do
      {:ok, task} = valid_attrs() |> Tasks.create_task
      assert task.modified_from == "code_corps"
    end

    test "returns errored changeset if attributes are invalid" do
      {:error, changeset} = Tasks.create_task(@base_attrs)
      refute changeset.valid?
      refute Repo.one(Task)

      refute_received({:post, "https://api.github.com/" <> _rest, _body, _headers, _options})
    end

    test "if task is assigned a github repo, creates github issue on assigned repo" do
      attrs = valid_attrs()
      project = Repo.one(CodeCorps.Project)
      github_repo =
        :github_repo
        |> insert(github_account_login: "foo", name: "bar")

      insert(:github_repo, project: project)

      {:ok, task} =
        attrs
        |> Map.put("github_repo_id", github_repo.id)
        |> Tasks.create_task

      assert task.title == @base_attrs["title"]
      assert task.markdown == @base_attrs["markdown"]
      assert task.body
      assert task.status == "open"
      assert task.github_issue_id
      assert task.github_repo_id == github_repo.id

      assert_received({:post, "https://api.github.com/repos/foo/bar/issues", _body, _headers, _options})
    end

    test "if github process fails, returns {:error, :github}" do
      attrs = valid_attrs()
      project = Repo.one(CodeCorps.Project)
      github_repo =
        :github_repo
        |> insert(github_account_login: "foo", name: "bar")

      insert(:github_repo, project: project)

      with_mock_api(CodeCorps.GitHub.FailureAPI) do
        assert {:error, :github} ==
          attrs
          |> Map.put("github_repo_id", github_repo.id)
          |> Tasks.create_task
      end

      refute Repo.one(Task)
      assert_received({:post, "https://api.github.com/repos/foo/bar/issues", _body, _headers, _options})
    end
  end

  describe "update_task/2" do
    @update_attrs %{"title" => "foo", "markdown" => "bar", "status" => "closed"}

    test "updates task" do
      task = insert(:task)
      {:ok, updated_task} = task |> Tasks.update_task(@update_attrs)

      assert updated_task.id == task.id
      assert updated_task.title == @update_attrs["title"]
      assert updated_task.markdown == @update_attrs["markdown"]
      refute updated_task.body == task.body
      refute task.github_issue_id
      refute task.github_repo_id

      refute_received({:patch, "https://api.github.com/" <> _rest, _body, _headers, _options})
    end

    test "sets modified_from to 'code_corps'" do
      task = insert(:task, modified_from: "github")
      {:ok, updated_task} = task |> Tasks.update_task(@update_attrs)

      assert updated_task.modified_from == "code_corps"
    end

    test "returns {:error, changeset} if there are validation errors" do
      task = insert(:task)
      {:error, changeset} = task |> Tasks.update_task(%{"title" => nil})

      refute changeset.valid?

      refute_received({:patch, "https://api.github.com/" <> _rest, _body, _headers, _options})
    end

    test "creates a github issue if task is just now connected to a repo" do
      github_repo =
        :github_repo
        |> insert(github_account_login: "foo", name: "bar")

      task = insert(:task)

      attrs = @update_attrs |> Map.put("github_repo_id", github_repo.id)

      {:ok, updated_task} = task |> Tasks.update_task(attrs)

      assert updated_task.github_issue_id
      assert updated_task.github_repo_id == github_repo.id

      assert_received({:post, "https://api.github.com/repos/foo/bar/issues", _body, _headers, _options})
    end

    test "propagates changes to github if task is synced to github issue" do
      github_repo =
        :github_repo
        |> insert(github_account_login: "foo", name: "bar")

      github_issue = insert(:github_issue, number: 5)
      task = insert(:task, github_repo: github_repo, github_issue: github_issue)

      {:ok, updated_task} = task |> Tasks.update_task(@update_attrs)

      assert updated_task.id == task.id
      assert updated_task.title == @update_attrs["title"]
      assert updated_task.markdown == @update_attrs["markdown"]
      refute updated_task.body == task.body
      assert updated_task.github_issue_id
      assert updated_task.github_repo_id

      assert_received({:patch, "https://api.github.com/repos/foo/bar/issues/5", _body, _headers, _options})
    end

    test "propagates changes to github if task is synced to github pull request" do
      %{
        "id" => issue_github_id,
        "number" => number
      } = @issue_payload

      github_repo = insert(:github_repo, github_account_login: "octocat", name: "Hello-World")
      github_pull_request = insert(:github_pull_request)
      github_issue = insert(:github_issue, github_id: issue_github_id, number: number, github_pull_request: github_pull_request, github_repo: github_repo)
      task = insert(:task, github_repo: github_repo, github_issue: github_issue)

      {:ok, updated_task} = task |> Tasks.update_task(@update_attrs)

      assert_received({:patch, "https://api.github.com/repos/octocat/Hello-World/issues/1347", _body, _headers, _options})

      assert updated_task.id == task.id
      assert updated_task.title == @update_attrs["title"]
      assert updated_task.markdown == @update_attrs["markdown"]
      refute updated_task.body == task.body
      assert updated_task.github_issue_id == github_issue.id
      assert updated_task.github_repo_id == github_repo.id

      updated_github_issue = Repo.one(GithubIssue)

      assert updated_github_issue.github_pull_request_id == github_pull_request.id
    end

    test "reports {:error, :github}, makes no changes at all if there is a github api error" do
      github_repo =
        :github_repo
        |> insert(github_account_login: "foo", name: "bar")

      github_issue = insert(:github_issue, number: 5)
      task = insert(:task, github_repo: github_repo, github_issue: github_issue)

      with_mock_api(CodeCorps.GitHub.FailureAPI) do
        assert {:error, :github} == task |> Tasks.update_task(@update_attrs)
      end

      updated_task = Repo.one(Task)

      assert updated_task.id == task.id
      assert updated_task.title == task.title
      assert updated_task.markdown == task.markdown
      assert updated_task.body == task.body
      assert updated_task.github_issue_id == task.github_issue_id
      assert updated_task.github_repo_id == task.github_repo_id

      assert_received({:patch, "https://api.github.com/repos/foo/bar/issues/5", _body, _headers, _options})
    end
  end
end
