#!/usr/bin/env perl
use strict;
use warnings;
use Test::More tests => 21;

BEGIN { use_ok( 'Net::DHCP::Packet' ); }
BEGIN { use_ok( 'Net::DHCP::Constants', ':ra_codes' ); }
BEGIN { use_ok( 'Net::DHCP::Constants', ':dhcp_other' ); }
BEGIN { use_ok( 'Net::DHCP::Constants', ':dho_codes' ); }
BEGIN { use_ok( 'Net::DHCP::Constants', ':geoconf_codes' ); }
BEGIN { use_ok( 'Net::DHCP::Constants', ':nwip_codes' ); }
BEGIN { use_ok( 'Net::DHCP::Constants', ':ccc_codes' ); }
BEGIN { use_ok( 'Net::DHCP::Constants', ':vendor43_codes' ); }

sub pack_subopt {
    my ($type, $val) = @_;
    return pack("C C a*", $type, length($val), $val);
}

subtest 'link selection suboption (5) with inet format' => sub {
    plan tests => 2;
    my $p = Net::DHCP::Packet->new();
    $p->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_LINK_SELECTION(), '192.0.2.1');
    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_LINK_SELECTION()),
        '192.0.2.1', 'add/get link selection round-trip');
    is($p->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_LINK_SELECTION()),
        "\xC0\x00\x02\x01", 'raw link selection correct');
};

subtest 'subscriber id suboption (6) with string format' => sub {
    plan tests => 2;
    my $p = Net::DHCP::Packet->new();
    $p->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_SUBSCRIBER_ID(), 'user@isp');
    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_SUBSCRIBER_ID()),
        'user@isp', 'add/get subscriber id round-trip');
    is($p->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_SUBSCRIBER_ID()),
        'user@isp', 'raw subscriber id correct');
};

subtest 'relay agent flags suboption (10) with byte format' => sub {
    plan tests => 2;
    my $p = Net::DHCP::Packet->new();
    $p->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_FLAGS(), '1');
    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_FLAGS()),
        1, 'add/get flags round-trip');
    is($p->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_FLAGS()),
        "\x01", 'raw flags correct');
};

subtest 'server id override suboption (11) with inet format' => sub {
    plan tests => 2;
    my $p = Net::DHCP::Packet->new();
    $p->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_SERVER_ID_OVERRIDE(), '203.0.113.1');
    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_SERVER_ID_OVERRIDE()),
        '203.0.113.1', 'add/get server id override round-trip');
    is($p->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_SERVER_ID_OVERRIDE()),
        "\xCB\x00\x71\x01", 'raw server id override correct');
};

subtest 'agent id suboption (3) with hexa format' => sub {
    plan tests => 2;
    my $p = Net::DHCP::Packet->new();
    $p->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_AGENT_ID(), 'aabbccdd');
    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_AGENT_ID()),
        'aabbccdd', 'add/get agent id round-trip');
    is($p->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_AGENT_ID()),
        "\xAA\xBB\xCC\xDD", 'raw agent id correct');
};

subtest 'structured circuit id — Cisco VLAN/Module/Port' => sub {
    plan tests => 6;
    my $p = Net::DHCP::Packet->new();

    # Build Cisco circuit-id: \x00\x04 + VLAN=10 (u16) + Module=2 (u8) + Port=3 (u8)
    my $cisco_bin = pack('C C n C C', 0x00, 0x04, 10, 2, 3);
    $p->addSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID(), $cisco_bin);

    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID()),
        'VLAN=10 Module=2 Port=3',
        'structured circuit-id decoded');

    # Round-trip via addSubOptionValue
    my $p2 = Net::DHCP::Packet->new();
    $p2->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID(),
        'VLAN=10 Module=2 Port=3');
    is($p2->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID()),
        'VLAN=10 Module=2 Port=3',
        'structured circuit-id pack/unpack round-trip');
    is($p2->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID()),
        $cisco_bin, 'raw structured circuit-id matches');

    # Legacy hex input still works
    my $p3 = Net::DHCP::Packet->new();
    $p3->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID(), 'aabb');
    is($p3->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID()),
        'aabb', 'legacy hex input still works');
    is($p3->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID()),
        "\xAA\xBB", 'raw legacy hex correct');

    # Vendor string format (\x01 prefix)
    my $p4 = Net::DHCP::Packet->new();
    $p4->addSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID(), "\x01switch-port-3");
    is($p4->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID()),
        'switch-port-3', 'circuit-id vendor string decoded');
};

subtest 'structured remote id — Cisco MAC' => sub {
    plan tests => 5;
    my $p = Net::DHCP::Packet->new();

    # Build Cisco remote-id: \x00\x06 + MAC (6 bytes)
    my $mac_bin = pack('C C a6', 0x00, 0x06, "\x00\x11\x22\x33\x44\x55");
    $p->addSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID(), $mac_bin);

    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID()),
        '00:11:22:33:44:55', 'structured remote-id MAC decoded');

    # Round-trip via addSubOptionValue with MAC input
    my $p2 = Net::DHCP::Packet->new();
    $p2->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID(),
        '00:11:22:33:44:55');
    is($p2->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID()),
        '00:11:22:33:44:55', 'structured remote-id pack/unpack round-trip');
    is($p2->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID()),
        $mac_bin, 'raw structured remote-id matches');

    # Legacy hex input still works
    my $p3 = Net::DHCP::Packet->new();
    $p3->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID(), 'ccdd');
    is($p3->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID()),
        'ccdd', 'legacy hex input still works for remote-id');
    is($p3->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_REMOTE_ID()),
        "\xCC\xDD", 'raw legacy hex correct for remote-id');
};

subtest 'vss suboptions (151, 152) with hexa format' => sub {
    plan tests => 2;
    my $p = Net::DHCP::Packet->new();
    $p->addSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_DHCPV4_VIRTUAL_SUBNET_SELECTION(), 'deadbeef');
    is($p->getSubOptionValue(DHO_DHCP_AGENT_OPTIONS(), RAI_DHCPV4_VIRTUAL_SUBNET_SELECTION()),
        'deadbeef', 'add/get VSS round-trip');
    is($p->getSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_DHCPV4_VIRTUAL_SUBNET_SELECTION()),
        "\xDE\xAD\xBE\xEF", 'raw VSS correct');
};

subtest 'printable-string heuristic in toString' => sub {
    plan tests => 3;
    my $p = Net::DHCP::Packet->new();

    # Hexa suboption with printable data (Agent ID with ASCII text)
    $p->addSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_AGENT_ID(), 'HelloAgent');
    my $out = $p->toString();
    like($out, qr/HelloAgent/, 'hexa suboption with printable data shown as string');

    # Hexa suboption with binary data (should stay hex)
    my $p2 = Net::DHCP::Packet->new();
    $p2->addSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_AGENT_ID(), "\xDE\xAD\xBE\xEF");
    my $out2 = $p2->toString();
    like($out2, qr/deadbeef/, 'hexa suboption with binary data shown as hex');

    # Structured circuit-id shows formatted string in toString
    my $p3 = Net::DHCP::Packet->new();
    $p3->addSubOptionRaw(DHO_DHCP_AGENT_OPTIONS(), RAI_CIRCUIT_ID(),
        pack('C C n C C', 0x00, 0x04, 5, 1, 2));
    my $out3 = $p3->toString();
    like($out3, qr/VLAN=5/, 'structured circuit-id shown formatted in toString');
};

subtest 'geoconf suboption (123) with int/byte format' => sub {
    plan tests => 5;
    my $p = Net::DHCP::Packet->new();

    # Does not croak
    eval { $p->getOptionValue(123) };
    is($@, '', 'getOptionValue(123) does not croak');

    # Meters as int (u32)
    $p->addSubOptionValue(DHO_GEOCONF(), GEO_METERS(), '100');
    is($p->getSubOptionValue(DHO_GEOCONF(), GEO_METERS()),
        100, 'add/get geoconf meters round-trip');
    is($p->getSubOptionRaw(DHO_GEOCONF(), GEO_METERS()),
        "\x00\x00\x00\x64", 'raw geoconf meters correct');

    # Floors as byte
    $p->addSubOptionValue(DHO_GEOCONF(), GEO_FLOORS(), '5');
    is($p->getSubOptionValue(DHO_GEOCONF(), GEO_FLOORS()),
        5, 'add/get geoconf floors round-trip');
    is($p->getSubOptionRaw(DHO_GEOCONF(), GEO_FLOORS()),
        "\x05", 'raw geoconf floors correct');
};

subtest 'nwip suboption (63) with byte/inet format' => sub {
    plan tests => 6;
    my $p = Net::DHCP::Packet->new();

    # Flag suboption (byte)
    $p->addSubOptionValue(DHO_NWIP_SUBOPTIONS(), NWIP_DOES_NOT_EXIST(), '1');
    is($p->getSubOptionValue(DHO_NWIP_SUBOPTIONS(), NWIP_DOES_NOT_EXIST()),
        1, 'nwip flag suboption round-trip');
    is($p->getSubOptionRaw(DHO_NWIP_SUBOPTIONS(), NWIP_DOES_NOT_EXIST()),
        "\x01",         'raw nwip flag correct');

    # IP suboption (inet)
    $p->addSubOptionValue(DHO_NWIP_SUBOPTIONS(), NWIP_PREFERRED_DSS(), '192.0.2.1');
    is($p->getSubOptionValue(DHO_NWIP_SUBOPTIONS(), NWIP_PREFERRED_DSS()),
        '192.0.2.1', 'nwip inet suboption round-trip');
    is($p->getSubOptionRaw(DHO_NWIP_SUBOPTIONS(), NWIP_PREFERRED_DSS()),
        "\xC0\x00\x02\x01", 'raw nwip inet correct');

    # No croak on getOptionValue
    eval { $p->getOptionValue(63) };
    is($@, '', 'getOptionValue(63) does not croak');

    # All suboption formats accessible
    my $p2 = Net::DHCP::Packet->new();
    $p2->addSubOptionValue(DHO_NWIP_SUBOPTIONS(), NWIP_1_1(), '0');
    is($p2->getSubOptionValue(DHO_NWIP_SUBOPTIONS(), NWIP_1_1()),
        0, 'nwip 1_1 suboption round-trip');
};

subtest 'ccc suboption (122) with inet/string/byte/int/hexa format' => sub {
    plan tests => 6;
    my $p = Net::DHCP::Packet->new();

    # inet suboption
    $p->addSubOptionValue(DHO_CCC(), CCC_PRIMARY_DHCP_SERVER(), '192.0.2.2');
    is($p->getSubOptionValue(DHO_CCC(), CCC_PRIMARY_DHCP_SERVER()),
        '192.0.2.2', 'ccc inet suboption round-trip');
    is($p->getSubOptionRaw(DHO_CCC(), CCC_PRIMARY_DHCP_SERVER()),
        "\xC0\x00\x02\x02", 'raw ccc inet correct');

    # string suboption
    $p->addSubOptionValue(DHO_CCC(), CCC_KERBEROS_REALM(), 'EXAMPLE.COM');
    is($p->getSubOptionValue(DHO_CCC(), CCC_KERBEROS_REALM()),
        'EXAMPLE.COM', 'ccc string suboption round-trip');

    # byte suboption
    $p->addSubOptionValue(DHO_CCC(), CCC_TICKET_SERVER_UTILIZATION(), '7');
    is($p->getSubOptionValue(DHO_CCC(), CCC_TICKET_SERVER_UTILIZATION()),
        7, 'ccc byte suboption round-trip');

    # int suboption (u32)
    $p->addSubOptionValue(DHO_CCC(), CCC_PROVISIONING_TIMER(), '3600');
    is($p->getSubOptionValue(DHO_CCC(), CCC_PROVISIONING_TIMER()),
        3600, 'ccc int suboption round-trip');
    is($p->getSubOptionRaw(DHO_CCC(), CCC_PROVISIONING_TIMER()),
        "\x00\x00\x0E\x10", 'raw ccc provisioning timer correct');
};

subtest 'vendor43 suboption (43) with byte/string/hexa format' => sub {
    plan tests => 8;
    my $p = Net::DHCP::Packet->new();

    # byte suboption (DEVICE_TYPE = 2)
    $p->addSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_DEVICE_TYPE(), '1');
    is($p->getSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_DEVICE_TYPE()),
        1, 'vendor43 byte suboption round-trip');
    is($p->getSubOptionRaw(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_DEVICE_TYPE()),
        "\x01", 'raw vendor43 byte correct');

    # string suboption (SERIAL_NUMBER = 4)
    $p->addSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_SERIAL_NUMBER(), 'SN12345');
    is($p->getSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_SERIAL_NUMBER()),
        'SN12345', 'vendor43 string suboption round-trip');
    is($p->getSubOptionRaw(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_SERIAL_NUMBER()),
        'SN12345', 'raw vendor43 string correct');

    # hexa suboption (OUI = 8)
    $p->addSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_OUI(), '001122');
    is($p->getSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_OUI()),
        '001122', 'vendor43 hexa suboption round-trip');
    is($p->getSubOptionRaw(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_OUI()),
        "\x00\x11\x22", 'raw vendor43 hexa correct');

    # MAC as hexa suboption (MTA_MAC_ADDRESS = 31)
    $p->addSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_MTA_MAC_ADDRESS(), 'aabbccddee');
    is($p->getSubOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS(), VENDOR43_MTA_MAC_ADDRESS()),
        'aabbccddee', 'vendor43 mac hexa suboption round-trip');

    # getOptionValue does not croak (even with hashref suboption data)
    eval { $p->getOptionValue(DHO_VENDOR_ENCAPSULATED_OPTIONS()) };
    is($@, '', 'getOptionValue(43) does not croak');
};
