use strict;
use warnings;

use DateTime::TimeZone::Local;
use File::Basename qw( basename );
use File::Spec;
use Sys::Hostname qw( hostname );
use Test::More;

use lib File::Spec->catdir( File::Spec->curdir, 't' );

BEGIN { require 'check_datetime_version.pl' }

plan skip_all => 'These tests only run on Windows'
    unless $^O =~ /win32/i;

my $Registry;
eval <<'EOF';
use DateTime::TimeZone::Local::Win32;
use Win32::TieRegistry ( TiedRef => \$Registry, Delimiter => q{/} );
EOF

if ($@) {
    plan skip_all => 'These tests require Win32::TieRegistry';
}

my $tzi_key = $Registry->Open(
    'LMachine/SYSTEM/CurrentControlSet/Control/TimeZoneInformation/', {
        Access => Win32::TieRegistry::KEY_READ()
            | Win32::TieRegistry::KEY_WRITE()
    }
);

plan skip_all =>
    'These tests require write access to TimeZoneInformation registry key'
    unless $tzi_key;

my $WindowsTZKey;

{
    foreach my $win_tz_name ( windows_tz_names() ) {
        set_and_test_windows_tz( $win_tz_name, undef, $tzi_key );
    }

    # We test these explicitly because we want to make sure that at
    # least a few known names do work, rather than just relying on
    # looping through a list.
    for my $pair (
        [ 'Eastern Standard Time',  'America/New_York' ],
        [ 'Dateline Standard Time', '-1200' ],
        [ 'Israel Standard Time',   'Asia/Jerusalem' ],
        ) {
        set_and_test_windows_tz( @{$pair}, $tzi_key );
    }
}

done_testing();

sub windows_tz_names {
    $WindowsTZKey = $Registry->Open(
        'LMachine/SOFTWARE/Microsoft/Windows NT/CurrentVersion/Time Zones/',
        { Access => Win32::TieRegistry::KEY_READ() }
    );

    $WindowsTZKey ||= $Registry->Open(
        'LMachine/SOFTWARE/Microsoft/Windows/CurrentVersion/Time Zones/',
        { Access => Win32::TieRegistry::KEY_READ() }
    );

    return unless $WindowsTZKey;

    return $WindowsTZKey->SubKeyNames();
}

sub set_and_test_windows_tz {
    my $windows_tz_name = shift;
    my $olson_name      = shift;
    my $tzi_key         = shift;

    if (   defined $tzi_key
        && defined $tzi_key->{'/TimeZoneKeyName'}
        && $tzi_key->{'/TimeZoneKeyName'} ne '' ) {
        local $tzi_key->{'/TimeZoneKeyName'} = $windows_tz_name;

        test_windows_zone( $windows_tz_name, $olson_name );
    }
    else {
        local $tzi_key->{'/StandardName'} = (
              $WindowsTZKey->{ $windows_tz_name . q{/} }
            ? $WindowsTZKey->{ $windows_tz_name . '/Std' }
            : 'MAKE BELIEVE VALUE'
        );

        test_windows_zone( $windows_tz_name, $olson_name );
    }
}

sub test_windows_zone {
    my $windows_tz_name = shift;
    my $olson_name      = shift;

    my %KnownBad = map { $_ => 1 } ();

    my $tz = DateTime::TimeZone::Local::Win32->FromRegistry();

    ok(
        $tz && DateTime::TimeZone->is_valid_name( $tz->name() ),
        "$windows_tz_name - found valid Olson time zone from Windows"
    );

    if ( defined $olson_name ) {
        my $desc = "$windows_tz_name was mapped to $olson_name";
        if ($tz) {
            is( $tz->name(), $olson_name, $desc );
        }
        else {
            fail($desc);
        }
    }
    else {
    SKIP: {
            if ( !$tz || !DateTime::TimeZone->is_valid_name( $tz->name() ) ) {
                skip(
                    "Time Zone display for $windows_tz_name not testable",
                    1
                );
            }
            my $dt = DateTime->new(
                year      => 2010,
                month     => 7,
                day       => 1,
                hour      => 12,
                time_zone => $tz->name(),
            );

            my $olson_offset = int( $dt->strftime("%z") );
            $olson_offset -= 100 if $dt->is_dst();
            my $windows_offset
                = $WindowsTZKey->{"${windows_tz_name}/Display"};

            if ( $windows_offset =~ /^\((?:GMT|UTC)\).*$/ ) {
                $windows_offset = 0;
            }
            else {
                if ( $windows_offset
                    =~ s/^\((?:GMT|UTC)(.*?):(.*?)\).*$/$1$2/ ) {
                    $windows_offset = int($windows_offset);
                }
                else {
                    skip(
                        "Time Zone display for $windows_tz_name not testable",
                        1
                    );
                }
            }

            unless ( -d '.hg' ) {
                skip(
                    "$windows_tz_name - Windows offset matches Olson offset (Maintainer only)",
                    1
                );
            }

            if ( $KnownBad{$windows_tz_name} ) {
            TODO: {
                    local $TODO
                        = "Microsoft has some out-of-date time zones relative to Olson";
                    is(
                        $olson_offset, $windows_offset,
                        "$windows_tz_name - Windows offset matches Olson offset"
                    );
                    return;
                }
            }
            elsif ( defined $WindowsTZKey->{"${windows_tz_name}/IsObsolete"}
                && $WindowsTZKey->{"${windows_tz_name}/IsObsolete"} eq
                "0x00000001" ) {
                skip(
                    "$windows_tz_name - deprecated by Microsoft",
                    1
                );
            }
            else {
                is(
                    $olson_offset, $windows_offset,
                    "$windows_tz_name - Windows offset matches Olson offset"
                );
            }
        }
    }
}
