defmodule Towerops.EctoTypes.MacAddressTest do
  use ExUnit.Case, async: true

  alias Towerops.EctoTypes.MacAddress

  describe "type/0" do
    test "returns :string" do
      assert MacAddress.type() == :string
    end
  end

  describe "cast/1" do
    test "casts nil" do
      assert MacAddress.cast(nil) == {:ok, nil}
    end

    test "casts valid colon-separated MAC addresses" do
      {:ok, mac} = MacAddress.cast("00:11:22:33:44:55")
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
      assert mac.binary == <<0, 17, 34, 51, 68, 85>>

      # Test uppercase
      {:ok, mac2} = MacAddress.cast("AA:BB:CC:DD:EE:FF")
      assert mac2.address == "aa:bb:cc:dd:ee:ff"
      assert mac2.binary == <<170, 187, 204, 221, 238, 255>>

      # Test mixed case
      {:ok, mac3} = MacAddress.cast("Ab:Cd:Ef:12:34:56")
      assert mac3.address == "ab:cd:ef:12:34:56"
    end

    test "casts valid hyphen-separated MAC addresses" do
      {:ok, mac} = MacAddress.cast("00-11-22-33-44-55")
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
      assert mac.binary == <<0, 17, 34, 51, 68, 85>>

      # Test uppercase with hyphens
      {:ok, mac2} = MacAddress.cast("AA-BB-CC-DD-EE-FF")
      assert mac2.address == "aa:bb:cc:dd:ee:ff"
    end

    test "casts valid dot-separated (Cisco) MAC addresses" do
      {:ok, mac} = MacAddress.cast("0011.2233.4455")
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
      assert mac.binary == <<0, 17, 34, 51, 68, 85>>

      # Test uppercase with dots
      {:ok, mac2} = MacAddress.cast("AABB.CCDD.EEFF")
      assert mac2.address == "aa:bb:cc:dd:ee:ff"
    end

    test "casts valid compact (no separator) MAC addresses" do
      {:ok, mac} = MacAddress.cast("001122334455")
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
      assert mac.binary == <<0, 17, 34, 51, 68, 85>>

      # Test uppercase compact
      {:ok, mac2} = MacAddress.cast("AABBCCDDEEFF")
      assert mac2.address == "aa:bb:cc:dd:ee:ff"
    end

    test "casts SNMP binary format (6 bytes)" do
      {:ok, mac} = MacAddress.cast(<<0, 17, 34, 51, 68, 85>>)
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
      assert mac.binary == <<0, 17, 34, 51, 68, 85>>

      {:ok, mac2} = MacAddress.cast(<<170, 187, 204, 221, 238, 255>>)
      assert mac2.address == "aa:bb:cc:dd:ee:ff"
    end

    test "is idempotent - accepts already cast structs" do
      {:ok, mac} = MacAddress.cast("00:11:22:33:44:55")
      assert MacAddress.cast(mac) == {:ok, mac}
    end

    test "normalizes all formats to colon-separated lowercase" do
      formats = [
        "00:11:22:33:44:55",
        "00-11-22-33-44-55",
        "0011.2233.4455",
        "001122334455",
        "00:11:22:33:44:55",
        "00:11:22:33:44:55"
      ]

      for format <- formats do
        {:ok, mac} = MacAddress.cast(format)
        assert mac.address == "00:11:22:33:44:55"
      end
    end

    test "rejects empty strings" do
      assert MacAddress.cast("") == :error
    end

    test "rejects invalid MAC address formats" do
      assert MacAddress.cast("not-a-mac") == :error
      assert MacAddress.cast("00:11:22:33:44") == :error
      assert MacAddress.cast("00:11:22:33:44:55:66") == :error
      assert MacAddress.cast("ZZ:11:22:33:44:55") == :error
      assert MacAddress.cast("00-11.22:33-44.55") == :error
      assert MacAddress.cast("0011.2233.44") == :error
      assert MacAddress.cast("001122") == :error
    end

    test "rejects binary with wrong size" do
      assert MacAddress.cast(<<0, 17, 34, 51, 68>>) == :error
      assert MacAddress.cast(<<0, 17, 34, 51, 68, 85, 102>>) == :error
    end

    test "rejects unsupported types" do
      assert MacAddress.cast(12_345) == :error
      assert MacAddress.cast(3.14) == :error
      assert MacAddress.cast(true) == :error
      assert MacAddress.cast([0, 17, 34, 51, 68, 85]) == :error
      assert MacAddress.cast(%{mac: "00:11:22:33:44:55"}) == :error
    end
  end

  describe "load/1" do
    test "loads nil" do
      assert MacAddress.load(nil) == {:ok, nil}
    end

    test "loads valid MAC strings from database" do
      {:ok, mac} = MacAddress.load("00:11:22:33:44:55")
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
      assert mac.binary == <<0, 17, 34, 51, 68, 85>>
    end

    test "returns error for invalid strings" do
      assert MacAddress.load("not-a-mac") == :error
      assert MacAddress.load("ZZ:11:22:33:44:55") == :error
    end

    test "returns error for non-string types" do
      assert MacAddress.load(12_345) == :error
      assert MacAddress.load([0, 17, 34, 51, 68, 85]) == :error
    end
  end

  describe "dump/1" do
    test "dumps nil" do
      assert MacAddress.dump(nil) == {:ok, nil}
    end

    test "dumps MAC struct to colon-separated lowercase string" do
      {:ok, mac} = MacAddress.cast("AA-BB-CC-DD-EE-FF")
      assert MacAddress.dump(mac) == {:ok, "aa:bb:cc:dd:ee:ff"}
    end

    test "returns error for invalid input" do
      assert MacAddress.dump("00:11:22:33:44:55") == :error
      assert MacAddress.dump(12_345) == :error
      assert MacAddress.dump(<<0, 17, 34, 51, 68, 85>>) == :error
      assert MacAddress.dump(%{address: "00:11:22:33:44:55"}) == :error
    end
  end

  describe "cast -> dump -> load roundtrip" do
    test "nil roundtrips correctly" do
      {:ok, casted} = MacAddress.cast(nil)
      {:ok, dumped} = MacAddress.dump(casted)
      {:ok, loaded} = MacAddress.load(dumped)
      assert loaded == nil
    end

    test "colon format roundtrips correctly" do
      {:ok, casted} = MacAddress.cast("00:11:22:33:44:55")
      {:ok, dumped} = MacAddress.dump(casted)
      {:ok, loaded} = MacAddress.load(dumped)
      assert loaded.address == "00:11:22:33:44:55"
      assert loaded.binary == <<0, 17, 34, 51, 68, 85>>
    end

    test "hyphen format roundtrips to colon format" do
      {:ok, casted} = MacAddress.cast("AA-BB-CC-DD-EE-FF")
      {:ok, dumped} = MacAddress.dump(casted)
      {:ok, loaded} = MacAddress.load(dumped)
      assert loaded.address == "aa:bb:cc:dd:ee:ff"
    end

    test "dot format roundtrips to colon format" do
      {:ok, casted} = MacAddress.cast("AABB.CCDD.EEFF")
      {:ok, dumped} = MacAddress.dump(casted)
      {:ok, loaded} = MacAddress.load(dumped)
      assert loaded.address == "aa:bb:cc:dd:ee:ff"
    end

    test "compact format roundtrips to colon format" do
      {:ok, casted} = MacAddress.cast("AABBCCDDEEFF")
      {:ok, dumped} = MacAddress.dump(casted)
      {:ok, loaded} = MacAddress.load(dumped)
      assert loaded.address == "aa:bb:cc:dd:ee:ff"
    end

    test "SNMP binary roundtrips correctly" do
      {:ok, casted} = MacAddress.cast(<<170, 187, 204, 221, 238, 255>>)
      {:ok, dumped} = MacAddress.dump(casted)
      {:ok, loaded} = MacAddress.load(dumped)
      assert loaded.address == "aa:bb:cc:dd:ee:ff"
      assert loaded.binary == <<170, 187, 204, 221, 238, 255>>
    end
  end

  describe "new/1" do
    test "creates MacAddress from valid string" do
      {:ok, mac} = MacAddress.new("00:11:22:33:44:55")
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
    end

    test "creates MacAddress from valid binary" do
      {:ok, mac} = MacAddress.new(<<0, 17, 34, 51, 68, 85>>)
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
    end

    test "returns error for invalid input" do
      assert MacAddress.new("invalid") == :error
      assert MacAddress.new("ZZ:11:22:33:44:55") == :error
    end
  end

  describe "new!/1" do
    test "creates MacAddress from valid string" do
      mac = MacAddress.new!("00:11:22:33:44:55")
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
    end

    test "creates MacAddress from valid binary" do
      mac = MacAddress.new!(<<0, 17, 34, 51, 68, 85>>)
      assert %MacAddress{} = mac
      assert mac.address == "00:11:22:33:44:55"
    end

    test "raises ArgumentError for invalid input" do
      assert_raise ArgumentError, ~r/invalid MAC address/, fn ->
        MacAddress.new!("invalid")
      end

      assert_raise ArgumentError, ~r/invalid MAC address/, fn ->
        MacAddress.new!("ZZ:11:22:33:44:55")
      end
    end
  end

  describe "to_string/1" do
    test "converts MAC struct to canonical colon-separated string" do
      mac = MacAddress.new!("AA-BB-CC-DD-EE-FF")
      assert MacAddress.to_string(mac) == "aa:bb:cc:dd:ee:ff"
    end

    test "preserves already normalized format" do
      mac = MacAddress.new!("00:11:22:33:44:55")
      assert MacAddress.to_string(mac) == "00:11:22:33:44:55"
    end
  end

  describe "to_binary/1" do
    test "converts MAC struct to 6-byte binary" do
      mac = MacAddress.new!("00:11:22:33:44:55")
      assert MacAddress.to_binary(mac) == <<0, 17, 34, 51, 68, 85>>
    end

    test "handles uppercase input" do
      mac = MacAddress.new!("AA:BB:CC:DD:EE:FF")
      assert MacAddress.to_binary(mac) == <<170, 187, 204, 221, 238, 255>>
    end
  end

  describe "format/2" do
    setup do
      {:ok, mac: MacAddress.new!("00:11:22:33:44:55")}
    end

    test "formats as colon-separated", %{mac: mac} do
      assert MacAddress.format(mac, :colon) == "00:11:22:33:44:55"
    end

    test "formats as hyphen-separated", %{mac: mac} do
      assert MacAddress.format(mac, :hyphen) == "00-11-22-33-44-55"
    end

    test "formats as dot-separated (Cisco)", %{mac: mac} do
      assert MacAddress.format(mac, :dot) == "0011.2233.4455"
    end

    test "formats as compact (no separators)", %{mac: mac} do
      assert MacAddress.format(mac, :compact) == "001122334455"
    end

    test "all formats are lowercase" do
      mac = MacAddress.new!("AA:BB:CC:DD:EE:FF")
      assert MacAddress.format(mac, :colon) == "aa:bb:cc:dd:ee:ff"
      assert MacAddress.format(mac, :hyphen) == "aa-bb-cc-dd-ee-ff"
      assert MacAddress.format(mac, :dot) == "aabb.ccdd.eeff"
      assert MacAddress.format(mac, :compact) == "aabbccddeeff"
    end
  end

  describe "special MAC addresses" do
    test "broadcast address" do
      {:ok, mac} = MacAddress.cast("FF:FF:FF:FF:FF:FF")
      assert mac.address == "ff:ff:ff:ff:ff:ff"
      assert mac.binary == <<255, 255, 255, 255, 255, 255>>
    end

    test "zero address" do
      {:ok, mac} = MacAddress.cast("00:00:00:00:00:00")
      assert mac.address == "00:00:00:00:00:00"
      assert mac.binary == <<0, 0, 0, 0, 0, 0>>
    end

    test "multicast address (odd first octet)" do
      {:ok, mac} = MacAddress.cast("01:00:5E:00:00:01")
      assert mac.address == "01:00:5e:00:00:01"
    end
  end
end
