defmodule Poison.DecoderTest do
  use ExUnit.Case, async: true

  import Poison.Decode, only: [transform: 2]

  defmodule Person do
    defstruct [:name, :address, :contact, age: 42]
  end

  defmodule Contact do
    defstruct [:email, :telephone]
  end

  defmodule Address do
    defstruct [:street, :city, :state, :zip]
  end

  defmodule Person2 do
    defstruct name: nil, age: 42, contacts: []
  end

  defmodule Contact2 do
    defstruct [:email, :telephone, call_count: 0]
  end

  defimpl Poison.Decoder, for: Address do
    def decode(address, _options) do
      "#{address.street}, #{address.city}, #{address.state}  #{address.zip}"
    end
  end

  test "decoding single :as with string keys" do
    person = %{"name" => "Devin Torres", "age" => 27}
    expected = %Person{name: "Devin Torres", age: 27}
    assert transform(person, %{as: %Person{}}) == expected
  end

  test "decoding single :as with atom keys" do
    person = %{name: "Devin Torres", age: 27}
    expected = %Person{name: "Devin Torres", age: 27}
    assert transform(person, %{keys: :atoms!, as: %Person{}}) == expected
  end

  test "decoding :as list with string keys" do
    person = [%{"name" => "Devin Torres", "age" => 27}]
    expected = [%Person{name: "Devin Torres", age: 27}]
    assert transform(person, %{as: [%Person{}]}) == expected
  end

  test "decoding nested :as with string keys" do
    person = %{"person" => %{"name" => "Devin Torres", "age" => 27}}
    actual = transform(person, %{as: %{"person" => %Person{}}})
    expected = %{"person" => %Person{name: "Devin Torres", age: 27}}
    assert actual == expected
  end

  test "decoding nested :as with atom keys" do
    person = %{person: %{name: "Devin Torres", age: 27}}
    actual = transform(person, %{keys: :atoms!, as: %{person: %Person{}}})
    expected = %{person: %Person{name: "Devin Torres", age: 27}}
    assert actual == expected
  end

  test "decoding nested :as list with string keys" do
    people = %{"people" => [%{"name" => "Devin Torres", "age" => 27}]}
    actual = transform(people, %{as: %{"people" => [%Person{}]}})
    expected = %{"people" => [%Person{name: "Devin Torres", age: 27}]}
    assert actual == expected
  end

  test "decoding into structs with key subset" do
    person = %{"name" => "Devin Torres", "age" => 27, "dob" => "1987-01-29"}
    expected = %Person{name: "Devin Torres", age: 27}
    assert transform(person, %{as: %Person{}}) == expected
  end

  test "decoding into structs with default values" do
    person = %{"name" => "Devin Torres"}
    expected = %Person{name: "Devin Torres", age: 50}
    assert transform(person, %{as: %Person{age: 50}}) == expected
  end

  test "decoding into structs with unspecified default values" do
    person = %{"name" => "Devin Torres"}
    expected = %Person{name: "Devin Torres", age: 42}
    assert transform(person, %{as: %Person{}}) == expected
  end

  test "decoding into structs with unspecified default values and atom keys" do
    person = %{:name => "Devin Torres"}
    expected = %Person{name: "Devin Torres", age: 42}
    assert transform(person, %{as: %Person{}, keys: :atoms!}) == expected
  end

  test "decoding into structs with nil overriding defaults" do
    person = %{"name" => "Devin Torres", "age" => nil}
    expected = %Person{name: "Devin Torres", age: nil}
    assert transform(person, %{as: %Person{}}) == expected
  end

  test "decoding into nested structs" do
    person = %{
      "name" => "Devin Torres",
      "contact" => %{"email" => "devin@torres.com"}
    }

    expected = %Person{
      name: "Devin Torres",
      contact: %Contact{email: "devin@torres.com"}
    }

    assert transform(person, %{as: %Person{contact: %Contact{}}}) == expected
  end

  test "decoding into nested struct, empty nested struct" do
    person = %{"name" => "Devin Torres"}
    expected = %Person{name: "Devin Torres"}
    assert transform(person, %{as: %Person{contact: %Contact{}}}) == expected
  end

  test "decoding into nested struct list" do
    person = %{
      "name" => "Devin Torres",
      "contacts" => [
        %{"email" => "devin@torres.com", "call_count" => 10},
        %{"email" => "test@email.com"}
      ]
    }

    expected = %Person2{
      name: "Devin Torres",
      contacts: [
        %Contact2{email: "devin@torres.com", call_count: 10},
        %Contact2{email: "test@email.com", call_count: 0}
      ]
    }

    decoded = transform(person, %{as: %Person2{contacts: [%Contact2{}]}})
    assert decoded == expected
  end

  test "decoding into nested struct list with keys = :atoms" do
    person = %{
      name: "Devin Torres",
      contacts: [
        %{email: "devin@torres.com", call_count: 10},
        %{email: "test@email.com"}
      ]
    }

    expected = %Person2{
      name: "Devin Torres",
      contacts: [
        %Contact2{email: "devin@torres.com", call_count: 10},
        %Contact2{email: "test@email.com", call_count: 0}
      ]
    }

    as = %Person2{contacts: [%Contact2{}]}
    decoded = transform(person, %{as: as, keys: :atoms})
    assert decoded == expected
  end

  test "decoding into nested structs, empty list" do
    person = %{"name" => "Devin Torres"}

    expected = %Person2{
      name: "Devin Torres",
      contacts: []
    }

    as = %Person2{contacts: [%Contact{}]}
    assert transform(person, %{as: as}) == expected
  end

  test "decoding into nested structs list with nil overriding default" do
    person = %{"name" => "Devin Torres", "contacts" => nil}
    expected = %Person2{name: "Devin Torres", contacts: nil}
    as = %Person2{contacts: [%Contact{}]}
    assert transform(person, %{as: as}) == expected
  end

  test "decoding into nested structs with nil overriding defaults" do
    person = %{"name" => "Devin Torres", "contact" => nil}
    expected = %Person{name: "Devin Torres", contact: nil}
    assert transform(person, %{as: %Person{contact: %Contact{}}}) == expected
  end

  test "decoding using a defined decoder" do
    address = %{
      "street" => "1 Main St.",
      "city" => "Austin",
      "state" => "TX",
      "zip" => "78701"
    }

    assert transform(address, %{as: %Address{}}) ==
             "1 Main St., Austin, TX  78701"
  end

  test "decoding a sigle :as function with string keys" do
    person = %{"name" => "Devin Torres"}
    as = fn %{"name" => _name} -> %Person{} end
    expected = %Person{name: "Devin Torres"}
    assert transform(person, %{as: as}) == expected
  end

  test "decoding a single :as function with atom keys" do
    person = %{name: "Devin Torres"}
    as = fn %{name: _name} -> %Person{} end
    expected = %Person{name: "Devin Torres"}
    assert transform(person, %{as: as, keys: :atoms!}) == expected
  end

  test "decoding a :as list function with string keys" do
    person = [%{"name" => "Devin Torres"}]
    as = fn _value -> [%Person{}] end
    expected = [%Person{name: "Devin Torres"}]
    assert transform(person, %{as: as}) == expected
  end

  test "decoding nested :as function with string keys" do
    person = %{"person" => %{"name" => "Devin Torres"}}
    as = fn _value -> %{"person" => %Person{}} end
    actual = transform(person, %{as: as})
    expected = %{"person" => %Person{name: "Devin Torres"}}
    assert actual == expected
  end

  test "decoding nested structs in :as function with string keys" do
    person = %{"name" => "Devin Torres", "contact" => %{"email" => "test@email.com"}}
    as = fn _value -> %Person{contact: %Contact{}} end
    expected = %Person{name: "Devin Torres", contact: %Contact{email: "test@email.com"}}
    assert transform(person, %{as: as}) == expected
  end
end
