use strict;
use warnings;
use blib;

use Test::More tests => 43;

use constant valid_mfrom_identity   => ( identity   => 'fred@example.com' );
use constant valid_ip_address       => ( ip_address => '192.168.0.1' );


#### Class Compilation ####

BEGIN { use_ok('Mail::SPF::Request') }


#### Basic Instantiation ####

{
    my $request = eval { Mail::SPF::Request->new(
        versions    => [1, 2],
        scope       => 'mfrom',
        identity    => 'fred@example.com',
        ip_address  => '192.168.0.1',
        helo_identity
                    => 'mta.example.com'
    ) };

    $@ eq '' and isa_ok($request, 'Mail::SPF::Request', 'Basic request object')
        or BAIL_OUT("Basic request instantiation failed: $@");

    # Have options been interpreted correctly?
    is_deeply([$request->versions], [1, 2],             'Basic request versions()');
    is($request->scope,             'mfrom',            'Basic request scope()');
    is($request->authority_domain,  'example.com',      'Basic request authority_domain()');
    is($request->identity,          'fred@example.com', 'Basic request identity()');
    is($request->domain,            'example.com',      'Basic request domain()');
    is($request->localpart,         'fred',             'Basic request localpart()');
    my $ip_address = $request->ip_address;
    isa_ok($ip_address,             'NetAddr::IP',      'Basic request ip_address()');
    is($ip_address,                 '192.168.0.1/32',   'Basic request ip_address()');
    is($ip_address->version,        4,                  'Basic request ip_address() IP version');
    my $ip_address_v6 = $request->ip_address_v6;
    isa_ok($ip_address_v6,          'NetAddr::IP',      'Basic request ip_address_v6()');
    is($ip_address_v6, NetAddr::IP->new('::ffff:192.168.0.1'), 'Basic request ip_address_v6()');
    is($ip_address_v6->version,     6,                  'Basic request ip_address_v6() IP version');
    is($request->helo_identity,     'mta.example.com',  'Basic request helo_identity()');

    # Request object cloning:
    my $request_clone = eval { $request->new( ip_address => '192.168.0.254' ) };
    isa_ok($request_clone, 'Mail::SPF::Request', 'Clone request object');
    is($request_clone->identity,    'fred@example.com', 'Clone request inherited identity()');
    is($request_clone->ip_address,  '192.168.0.254/32', 'Clone request override ip_address()');
}


#### Minimally Parameterized MAIL FROM Request ####

{
    my $request = eval { Mail::SPF::Request->new(
        identity    => 'fred@example.com',
        ip_address  => '192.168.0.1'
    ) };

    $@ eq '' and isa_ok($request, 'Mail::SPF::Request', 'Minimal MAIL FROM request object')
        or BAIL_OUT("Minimal MAIL FROM request instantiation failed: $@");

    # Have omitted options been deduced correctly?
    is_deeply([$request->versions], [1, 2],             'Minimal MAIL FROM request versions()');
    is($request->scope,             'mfrom',            'Minimal MAIL FROM request scope()');
    is($request->authority_domain,  'example.com',      'Minimal MAIL FROM request authority_domain()');
    is($request->helo_identity,     undef,              'Minimal MAIL FROM request helo_identity()');
}


#### Minimally Parameterized HELO Request ####

{
    my $request = eval { Mail::SPF::Request->new(
        scope       => 'helo',
        identity    => 'mta.example.com',
        valid_ip_address
    ) };

    $@ eq '' and isa_ok($request, 'Mail::SPF::Request', 'Minimal HELO request object')
        or BAIL_OUT("Minimal HELO request instantiation failed: $@");

    # Have omitted options been deduced correctly?
    is_deeply([$request->versions], [1],                'Minimal HELO request versions()');
    is($request->authority_domain,  'mta.example.com',  'Minimal HELO request authority_domain()');
    is($request->localpart,         'postmaster',       'Minimal HELO request default localpart()');
    is($request->helo_identity,     'mta.example.com',  'Minimal HELO request helo_identity()');
}


#### Versions Validation ####

{
    my $request;
    
    $request = Mail::SPF::Request->new(
        versions    => 1,
        valid_mfrom_identity,
        valid_ip_address
    );
    is_deeply([$request->versions], [1],                'versions => $string supported');
    
    eval { Mail::SPF::Request->new(
        versions    => {},  # Illegal versions option type!
        valid_mfrom_identity,
        valid_ip_address
    ) };
    isa_ok($@, 'Mail::SPF::EInvalidOptionValue',        'versions => $non_string_or_array illegal');
    
    eval { Mail::SPF::Request->new(
        versions    => [1, 666],  # Illegal version number!
        valid_mfrom_identity,
        valid_ip_address
    ) };
    isa_ok($@, 'Mail::SPF::EInvalidOptionValue',        'Detect illegal versions');
    
    $request = Mail::SPF::Request->new(
        versions    => [1, 2],
        scope       => 'helo',
        identity    => 'mta.example.com',
        valid_ip_address
    );
    is_deeply([$request->versions], [1],                'Drop versions irrelevant for scope');
}


#### Scope Validation ####

{
    eval { Mail::SPF::Request->new(
        scope       => 'foo',
        valid_mfrom_identity,
        valid_ip_address
    ) };
    isa_ok($@, 'Mail::SPF::EInvalidScope',              'Detect invalid scope');

    eval { Mail::SPF::Request->new(
        versions    => 1,
        scope       => 'pra',
        valid_mfrom_identity,
        valid_ip_address
    ) };
    isa_ok($@, 'Mail::SPF::EInvalidScope',              'Detect invalid scope for versions');
}


#### Identity Validation ####

{
    my $request;
    
    eval { Mail::SPF::Request->new(
        valid_ip_address
    ) };
    isa_ok($@, 'Mail::SPF::EOptionRequired',            'Detect missing identity option');

    $request = Mail::SPF::Request->new(
        scope       => 'mfrom',
        identity    => 'mta.example.com',  # Empty MAIL FROM, supply HELO domain.
        valid_ip_address
    );
    is($request->domain,            'mta.example.com',  'Extract domain from identity correctly');
    is($request->localpart,         'postmaster',       'Default "postmaster" localpart');
}


#### IP Address Validation ####

{
    my $request;
    
    eval { Mail::SPF::Request->new(
        valid_mfrom_identity
    ) };
    isa_ok($@, 'Mail::SPF::EOptionRequired',            'Detect missing ip_address option');

    my $ip_address = NetAddr::IP->new('192.168.0.1');
    $request = Mail::SPF::Request->new(
        valid_mfrom_identity,
        ip_address  => $ip_address
    );
    is($request->ip_address,        $ip_address,        'Accept NetAddr::IP object for ip_address');

    $request = Mail::SPF::Request->new(
        valid_mfrom_identity,
        ip_address  => '::ffff:192.168.0.1'
    );
    is($request->ip_address,        '192.168.0.1/32',   'Treat IPv4-mapped IPv6 address as IPv6 address');
}


#### Custom Request State ####

{
    my $request = Mail::SPF::Request->new(
        valid_mfrom_identity,
        valid_ip_address
    );

    is($request->state('uninitialized'), undef,         'Read uninitialized state field');

    $request->state('foo', 'bar');
    is($request->state('foo'),      'bar',              'Write and read state field');

    my $request_clone = $request->new();  # Clone request object.
    $request_clone->state('foo', 'boo');
    is($request->state('foo'),      'bar',              'Original state unaffected when modifying clone state');
}
