use strict;
use warnings;

use Test::More;
use Test::Exception;

use GDPR::IAB::TCFv2;

my $tc_v23
  = 'COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA.ILrtR_G__bXlv-bb36ftkeYxf9_hr7sQxBgbJs24FzLvW7JwX32E7NEzatqYKmRIAu3TBIQNtHJjURVChKIgVrzDsaEyUoTtKJ-BkiHMRY2NYCFxvm4tjWQCZ5vr_91d9mT-N7dr-2dzyy7hnv3a9_-S1WJidKYetHfv8bBKT-_IU9_x-_4v4_N7pE2-eS1v_tGvt639-4vP_dpvxt-7yffz____73_e7X__d_______Xf_7____________4AAA';

subtest "TCF v2.3 segments" => sub {
  my $consent;
  lives_ok {
    $consent = GDPR::IAB::TCFv2->Parse($tc_v23);
  }
  'should parse v2.3 string with disclosed vendors';

  is $consent->version, 2, 'version should be 2';

  # Disclosed vendors check
  ok $consent->disclosed_vendor(284),   'Weborama (284) should be disclosed';
  ok !$consent->disclosed_vendor(9999), 'vendor 9999 should NOT be disclosed';

  # Allowed vendors check (not present in this string)
  ok !$consent->allowed_vendor(284), 'vendor 284 should NOT be in allowed vendors segment (segment missing)';
};

subtest "duplicate segment check" => sub {
  my $duplicate_string = $tc_v23
    . '.ILrtR_G__bXlv-bb36ftkeYxf9_hr7sQxBgbJs24FzLvW7JwX32E7NEzatqYKmRIAu3TBIQNtHJjURVChKIgVrzDsaEyUoTtKJ-BkiHMRY2NYCFxvm4tjWQCZ5vr_91d9mT-N7dr-2dzyy7hnv3a9_-S1WJidKYetHfv8bBKT-_IU9_x-_4v4_N7pE2-eS1v_tGvt639-4vP_dpvxt-7yffz____73_e7X__d_______Xf_7____________4AAA';
  throws_ok {
    GDPR::IAB::TCFv2->Parse($duplicate_string);
  }
  qr/duplicate segment type 1/, 'should throw exception for duplicate segment type 1';
};

subtest "TCF v2.3 Mandatory segment" => sub {
  my $tc_v23_no_dv
    = 'CP188cAQKFpAAAHABBENBSFsAP_gAEPgAAiQKqNX_H__bW9r8X73aft0eY1P9_j77uQxBhfJE-4FzLvW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJHUTVigaogVryHMak2cpTNKJ6BkiFMRM2dYCF5vm4tj-QKY5_r993dx2D-t_dv83dzyz81Hn3f5_2e0eLCdQ5-tDfv9bROb-9IPd_78v4v8_l_rk2_eT1n_tevr7D_-ft8__XW_9_fff_9Pn_-uB_-_3_vf_EFUwCTDQqIA-wJCQg0DCKBACoKwgIoFAQAAJA0QEAJgwKdgYALrCRACAFAAMEAIAAQZAAgAAAgAQiACQAoEAAEAgUAAYAEAwEABAwAAgAsBAIAAQHQMUwIIFAsIEjMioUwIQoEggJbKhBICgQVwhCLPAIgERMFAAgAAAVgACAsFgcSSAlQkECXUG0AABAAgFEIFQgk9MAAwJmy1B4MG0ZWmAYPmCRDTAMgCIIyEAAAA.f_wACHwAAAAA';

  lives_ok { GDPR::IAB::TCFv2->Parse($tc_v23_no_dv) }
  'should parse by default (lenient)';

  throws_ok { GDPR::IAB::TCFv2->Parse($tc_v23_no_dv, strict => 1) }
  qr/Disclosed Vendors segment is mandatory/, 'should die in strict mode if DV segment is missing for v2.3';
};

subtest "TCF v2.2/v2.3 Legitimate Interest Restrictions" => sub {

  # TCF v2.0 string with P1 LI bit set (COwAdDh...)
  my $tc_v20 = 'COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA';
  my $c20    = GDPR::IAB::TCFv2->Parse($tc_v20);
  is($c20->policy_version,                                  2, 'v2.0 policy');
  is($c20->_safe_is_purpose_legitimate_interest_allowed(1), 1, 'P1 LI bit is 1');
  is($c20->is_purpose_legitimate_interest_allowed(1),       0, 'P1 LI logic returns 0 (forbidden)');
  is($c20->is_purpose_legitimate_interest_allowed(3),       1, 'P3 LI allowed in v2.0');

  # TCF v2.3 string (CP188...)
  my $tc_v23
    = 'CP188cAQKFpAAAHABBENBSFsAP_gAEPgAAiQKqNX_H__bW9r8X73aft0eY1P9_j77uQxBhfJE-4FzLvW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJHUTVigaogVryHMak2cpTNKJ6BkiFMRM2dYCF5vm4tj-QKY5_r993dx2D-t_dv83dzyz81Hn3f5_2e0eLCdQ5-tDfv9bROb-9IPd_78v4v8_l_rk2_eT1n_tevr7D_-ft8__XW_9_fff_9Pn_-uB_-_3_vf_EFUwCTDQqIA-wJCQg0DCKBACoKwgIoFAQAAJA0QEAJgwKdgYALrCRACAFAAMEAIAAQZAAgAAAgAQiACQAoEAAEAgUAAYAEAwEABAwAAgAsBAIAAQHQMUwIIFAsIEjMioUwIQoEggJbKhBICgQVwhCLPAIgERMFAAgAAAVgACAsFgcSSAlQkECXUG0AABAAgFEIFQgk9MAAwJmy1B4MG0ZWmAYPmCRDTAMgCIIyEAAAA.f_wACHwAAAAA';
  my $c23 = GDPR::IAB::TCFv2->Parse($tc_v23);
  is($c23->policy_version, 5, 'v2.3 policy');

# Even if bits were set (need a string with bits set to be 100% sure, but logic is verified)
  is($c23->is_purpose_legitimate_interest_allowed(3),  0, 'P3 LI forbidden in v2.3');
  is($c23->is_purpose_legitimate_interest_allowed(10), 1, 'P10 LI allowed in v2.3');

  # Flexible purpose check
  # Purpose 10 allows LI. Vendor 46 has LI but no Consent.
  # Default LI=1 should return 1.
  ok($c23->is_vendor_allowed_for_flexible_purpose(46, 10, 1), 'P10 flexible with default LI returns 1 for vendor 46');

  # Purpose 3 prohibits LI in v2.3. Vendor 46 has LI but no Consent.
  # Default LI=1 should return 0 (forced fallback to Consent).
  ok(!$c23->is_vendor_allowed_for_flexible_purpose(46, 3, 1),
    'P3 flexible with default LI returns 0 for vendor 46 (forced consent)');
};

subtest "MaxVendorId == 0 yields an empty vendor section" => sub {
  my $consent = GDPR::IAB::TCFv2->Parse('COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA');

  # Hand-built Disclosed Vendors segment:
  #   segment_type      = 001 (=1)
  #   max_vendor_id     = 16 zero bits
  #   is_range_encoding = 1
  #   num_entries       = 12 zero bits
  # With IsRange=1 and max_id=0, RangeSection->Parse would parse the
  # 12-bit num_entries from the trailing zeros (NumEntries=0, harmless
  # here, but a malicious payload could declare NumEntries>0).  The
  # short-circuit must skip RangeSection entirely and return an empty
  # BitField regardless of the IsRange flag.
  my $segment = '001' . ('0' x 16) . '1' . ('0' x 12);

  my $section;
  lives_ok {
    $section
      = $consent->_parse_vendor_bitfield_or_range($segment, GDPR::IAB::TCFv2::SEGMENT_TYPES->{DISCLOSED_VENDORS},);
  }
  'max_id=0 segment parses without error';

  ok defined $section, 'returns a defined section';
  is $section->max_id, 0, 'max_id is 0';
  ok !$section->contains(1), 'contains(1) returns falsey (early-exit on id > max_id)';
};

subtest "Disclosed Vendors helper rejects mis-typed payload" => sub {

  # Hand-craft a "Disclosed Vendors" segment whose first 3 bits claim
  # segment_type=2 (Allowed Vendors) instead of 1.  The router would
  # never feed this through _parse_disclosed_vendors today, but the
  # helper itself should still reject it as defense-in-depth.
  my $consent = GDPR::IAB::TCFv2->Parse('COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA');

  # 3 bits = 010 (=2), then a benign MaxVendorId=0, IsRangeEncoding=0 tail.
  my $bad = '010' . ('0' x 16) . '0';

  throws_ok {
    $consent->_parse_vendor_bitfield_or_range($bad, GDPR::IAB::TCFv2::SEGMENT_TYPES->{DISCLOSED_VENDORS},);
  }
  qr/invalid segment type/, 'helper croaks when payload header does not match expected type';
};

subtest "Core BitField data_size reflects slice length, not full core" => sub {

  # Single-segment v2.0 string with a sizeable core bitfield
  # (max_vendor_id_consent = 115).  Truncating 4 base64 chars chops
  # ~24 bits off the bitfield tail.
  #
  # OLD behavior: data_size = length(core_data) > max_id, so the
  # BitField guard never fires; the parser stumbles forward and
  # eventually croaks deep in _parse_publisher_section with a
  # misleading "missing 'core_data'" error.
  # NEW behavior: data_size = length(slice) < max_id, the guard
  # fires immediately with a clear "requires N bits" message that
  # points at the actual problem.
  my $good = 'CLcVDxRMWfGmWAVAHCENAXCkAKDAADnAABRgA5mdfCKZuYJez-NQm0TBMYA4oCAAGQYIAAAAAAEAIAEgAA';

  lives_ok { GDPR::IAB::TCFv2->Parse($good) } 'baseline parses cleanly';

  my $truncated = substr($good, 0, length($good) - 4);

  throws_ok { GDPR::IAB::TCFv2->Parse($truncated) }
  qr/a BitField for \d+ bits requires a consent string of at least \d+ bits/,
    'truncated core bitfield is rejected with slice-aware size guard';
};

done_testing;
