defmodule Towerops.ResultTest do
  use ExUnit.Case, async: true

  alias Towerops.Result

  doctest Result

  describe "map/2" do
    test "applies function to success value" do
      assert Result.map({:ok, 5}, fn x -> x * 2 end) == {:ok, 10}
      assert Result.map({:ok, "hello"}, &String.upcase/1) == {:ok, "HELLO"}
    end

    test "passes through error unchanged" do
      assert Result.map({:error, :not_found}, fn x -> x * 2 end) == {:error, :not_found}
      assert Result.map({:error, "failure"}, &String.upcase/1) == {:error, "failure"}
    end

    test "works with complex transformations" do
      result = {:ok, %{name: "John", age: 30}}
      assert Result.map(result, fn user -> user.name end) == {:ok, "John"}
    end
  end

  describe "map_error/2" do
    test "applies function to error value" do
      assert Result.map_error({:error, :timeout}, fn _ -> :network_error end) ==
               {:error, :network_error}

      assert Result.map_error({:error, "db_error"}, &String.upcase/1) == {:error, "DB_ERROR"}
    end

    test "passes through success unchanged" do
      assert Result.map_error({:ok, 42}, fn _ -> :network_error end) == {:ok, 42}
      assert Result.map_error({:ok, "success"}, &String.upcase/1) == {:ok, "success"}
    end

    test "works with complex error transformations" do
      result = {:error, %{code: 500, message: "Internal Error"}}

      assert Result.map_error(result, fn err -> err.code end) == {:error, 500}
    end
  end

  describe "and_then/2" do
    test "chains successful operations" do
      result = {:ok, 5}

      assert Result.and_then(result, fn x -> {:ok, x * 2} end) == {:ok, 10}
    end

    test "stops on first error" do
      result = {:error, :not_found}

      assert Result.and_then(result, fn x -> {:ok, x * 2} end) == {:error, :not_found}
    end

    test "propagates error from chained function" do
      result = {:ok, 5}

      assert Result.and_then(result, fn _ -> {:error, :invalid} end) == {:error, :invalid}
    end

    test "works with nested operations" do
      result = {:ok, 10}

      final_result =
        result
        |> Result.and_then(fn x -> {:ok, x + 5} end)
        |> Result.and_then(fn x -> {:ok, x * 2} end)
        |> Result.and_then(fn x -> {:ok, "Result: #{x}"} end)

      assert final_result == {:ok, "Result: 30"}
    end

    test "short-circuits on error in chain" do
      result = {:ok, 10}

      final_result =
        result
        |> Result.and_then(fn x -> {:ok, x + 5} end)
        |> Result.and_then(fn _ -> {:error, :failed} end)
        |> Result.and_then(fn x -> {:ok, x * 2} end)

      assert final_result == {:error, :failed}
    end
  end

  describe "unwrap_or/2" do
    test "returns success value when ok" do
      assert Result.unwrap_or({:ok, 42}, 0) == 42
      assert Result.unwrap_or({:ok, "hello"}, "default") == "hello"
    end

    test "returns default value on error" do
      assert Result.unwrap_or({:error, :not_found}, 0) == 0
      assert Result.unwrap_or({:error, "failure"}, "default") == "default"
    end

    test "works with nil values" do
      assert Result.unwrap_or({:ok, nil}, "default") == nil
      assert Result.unwrap_or({:error, :not_found}, nil) == nil
    end

    test "works with complex default values" do
      default = %{name: "Unknown", age: 0}
      assert Result.unwrap_or({:error, :not_found}, default) == default
    end
  end

  describe "ok?/1" do
    test "returns true for success" do
      assert Result.ok?({:ok, 42}) == true
      assert Result.ok?({:ok, nil}) == true
      assert Result.ok?({:ok, []}) == true
    end

    test "returns false for error" do
      assert Result.ok?({:error, :not_found}) == false
      assert Result.ok?({:error, "failure"}) == false
      assert Result.ok?({:error, nil}) == false
    end
  end

  describe "error?/1" do
    test "returns true for error" do
      assert Result.error?({:error, :not_found}) == true
      assert Result.error?({:error, "failure"}) == true
      assert Result.error?({:error, nil}) == true
    end

    test "returns false for success" do
      assert Result.error?({:ok, 42}) == false
      assert Result.error?({:ok, nil}) == false
      assert Result.error?({:ok, []}) == false
    end
  end

  describe "combined operations" do
    test "map and and_then together" do
      result = {:ok, "5"}

      final_result =
        result
        |> Result.map(&String.to_integer/1)
        |> Result.and_then(fn x -> {:ok, x * 2} end)
        |> Result.map(fn x -> x + 3 end)

      assert final_result == {:ok, 13}
    end

    test "map_error after failed operation" do
      result = {:error, :db_timeout}

      final_result =
        result
        |> Result.map(fn x -> x * 2 end)
        |> Result.map_error(fn _ -> :service_unavailable end)

      assert final_result == {:error, :service_unavailable}
    end

    test "conditional unwrap based on ok?" do
      success = {:ok, 42}
      error = {:error, :not_found}

      assert Result.ok?(success) && Result.unwrap_or(success, 0) == 42
      refute Result.ok?(error)
      assert Result.unwrap_or(error, 0) == 0
    end
  end
end
