#!/usr/bin/env perl

use strict;
use warnings;
use lib '.'; # For the mock MapIt module

use Getopt::Long ':config' => qw(pass_through auto_help);

my ($run_server, $run_cypress, $vagrant, $wsl, $node, $config_file);
my ($cobrand, $coords, $area_id, $name, $body_cobrand, $mapit_url, $coverage);

BEGIN {
    $config_file = 'conf/general.yml-example';
    $cobrand = [qw(
        bathnes
        borsetshire
        brent
        bromley
        buckinghamshire
        fixmystreet
        hackney
        highwaysengland
        hounslow
        isleofwight
        lincolnshire
        merton
        northamptonshire
        oxfordshire
        peterborough
        shropshire
        tfl
        westminster
    )];
    $coords = '51.532851,-2.284277';
    $area_id = 2608;
    $name = 'Borsetshire';
    $body_cobrand = 'borsetshire';
    $mapit_url = 'https://mapit.uk/';
    $node = 'C:\Program Files\nodejs\node.exe';

    GetOptions(
        'coverage' => \$coverage,
        'config=s' => \$config_file,
        'server' => \$run_server,
        'cypress' => \$run_cypress,
        'vagrant' => \$vagrant,
        'wsl=s' => \$wsl,
        'node=s' => \$node,
        'cobrand=s@' => \$cobrand,
        'coords=s' => \$coords,
        'area_id=s' => \$area_id,
        'name=s' => \$name,
        'body_cobrand=s' => \$body_cobrand,
        'mapit_url=s' => \$mapit_url,
    );
    $cobrand = [ split(',', join(',', @$cobrand)) ];

    if ($vagrant && $wsl) {
        print 'You cannot use both --vagrant and --wsl';
        exit 1;
    }

    if ($coverage && (system('git', 'diff', '--quiet', 'web') >> 8)) {
        print 'Do not run coverage with changes in web, they will be lost';
        exit 1;
    }

    if (!$run_server && !$run_cypress) {
        # If asked for neither, run both
        $run_server = $run_cypress = 1;
    }

    use File::Basename qw(dirname);
    use File::Spec;
    my $d = dirname(File::Spec->rel2abs($0));
    if (!$vagrant && $run_server) {
        require "$d/../setenv.pl";
    }
}

if ($vagrant) {
    # Test inception
    system('vagrant ssh -- "cd fixmystreet && bin/browser-tests --server 2>/dev/null" &');

    require IO::Socket;
    sub check_connection {
        my $remote = IO::Socket::INET->new(Proto => "tcp", PeerAddr => "localhost", PeerPort => 3001) or return;
        $remote->autoflush(1);
        print $remote "GET / HTTP/1.0\r\n\r\n";
        while (<$remote>) { return 1; }
        0;
    }

    local $| = 1;
    print 'Waiting for test server';
    while (!check_connection()) {
        print '.'; sleep 1;
    }
    print " done\n";
    system('bin/browser-tests', '--cypress', @ARGV);
    system('vagrant', 'ssh', '--', 'kill $(cat /tmp/cypress-server.pid)');
    exit;
}

BEGIN {
    # setenv.pl above unloads File:: modules but we need them
    use File::Path qw(remove_tree);
}

sub coverage_setup {
    # Add instrumentation to all JS files under web/
    if (system('nyc', 'instrument', '--exclude', 'vendor', '--compact', 'false', 'web', 'webO') >> 8) {
        print 'Could not instrument JS files - are @cypress/code-coverage and nyc installed?';
        exit 1;
    }

    # Move the instrumented files on top of the originals
    while (glob("webO/js/*.js webO/cobrands/*/*.js")) {
        (my $new = $_) =~ s/webO/web/;
        rename $_, $new;
    }

    remove_tree('webO', { safe => 1 }); # Remove anything else left
}

sub coverage_teardown {
    remove_tree('.nyc_output', '.cypress/coverage', { safe => 1 }); # Remove old data and incorrect report
    rename '.cypress/.nyc_output', './.nyc_output'; # Move to top level so nyc can find JS files
    system('git', 'checkout', 'web'); # Remove instrumented JS files
    system('nyc', 'report', '--reporter=lcov'); # Generate correct report
    print "The JS coverage report is at coverage/lcov-report/index.html\n";
}

sub run {
    my $cmd = shift @ARGV;
    die "Must specify a cypress command\n" unless $cmd || !$run_cypress;

    if ($run_server) {
        require FixMyStreet::TestAppProve;
        require t::Mock::MapIt;
        require YAML;
        require Path::Tiny;
        my $asset_layers = YAML::Load(Path::Tiny::path('data/test-asset-layers.yml')->slurp);
        my $config_out = FixMyStreet::TestAppProve->get_config({
            config_file => $config_file,
            # Want this to be like .com
            ALLOWED_COBRANDS => $cobrand,
            MAPIT_URL => $mapit_url,
            BASE_URL => 'http://fixmystreet.localhost:3001',
            STAGING_FLAGS => { skip_checks => 1 },
            BING_MAPS_API_KEY => 'key',
            COBRAND_FEATURES => {
                category_groups => { map { $_ => 1 } @$cobrand },
                suggest_duplicates => { map { $_ => 1 } @$cobrand },
                asset_layers => $asset_layers,
            }
        });
        $ENV{FMS_OVERRIDE_CONFIG} = $config_out;

        # Set up, and load in some data
        system('bin/make_css', map { $_ eq 'fixmystreet' ? 'fixmystreet.com' : $_ } @$cobrand);
        system(
            'bin/fixmystreet.com/fixture',
            '--test_fixtures',
            '--nonrandom',
            '--coords', $coords,
            '--name', $name,
            '--cobrand', $body_cobrand,
            '--area-id', $area_id,
            '--commit'
        ) == 0 or die("Failed to run fixture script");
    }

    my $pid;
    if ($run_server && $run_cypress) {
        $pid = fork();
        die if not defined $pid;
    }

    if (($run_cypress && !$run_server) || $pid) {
        # Parent, run the test runner (then kill the child)
        my @cypress = ('cypress');
        if ($wsl) {
            @cypress = ('cmd.exe', '/c', $node, $wsl);
        }
        my @config = $coverage ? () : ('--config', 'pluginsFile=false,supportFile=cypress/support/commands.js');
        my $exit = system(@cypress, $cmd, @config, '--project', '.cypress', @ARGV);
        kill 'TERM', $pid if $pid;

        coverage_teardown() if $coverage;

        exit $exit >> 8;
    } else {
        # Child, run the server on port 3001
        require FixMyStreet;
        FixMyStreet->test_mode('cypress'); # So email doesn't try to send, and things know we're in Cypress
        local $ENV{FIXMYSTREET_APP_DEBUG} = 0;
        require Plack::Runner;
        my $runner = Plack::Runner->new;
        $runner->parse_options('--listen', ':3001', '-s', 'Starman', '--env', 'deployment', '--pid', '/tmp/cypress-server.pid');
        $runner->run;
    }
}

coverage_setup() if $coverage;
run();


__END__

=head1 NAME

browser-tests - Run Cypress browser tests, set up for FixMyStreet.

=head1 SYNOPSIS

browser-tests [running options] [fixture options] [cypress options]

 Running options:
   --config         provide an override general.yml file
   --server         only run the test server, not cypress
   --cypress        only run cypress, not the test server
   --vagrant        run test server inside Vagrant, cypress outside
   --wsl            provide path to cypress node script, to run test server inside WSL, cypress outside
   --help           this help message

 Fixture option:
   --cobrand        Cobrand(s) to use, default is fixmystreet,highwaysengland,northamptonshire,bathnes,bromley,buckinghamshire,isleofwight,peterborough,tfl,hackney,oxfordshire,westminster
   --coords         Default co-ordinates for created reports
   --area_id        Area ID to use for created body
   --name           Name to use for created body
   --body_cobrand   Cobrand to assign to created body
   --mapit_url      MapIt URL to use, default mock

Use browser-tests instead of running cypress directly, so that a clean
database is set up for Cypress to use, not affecting your normal dev database.
If you're running FixMyStreet in a VM, you can use this script to run the test
server in the VM and Cypress outside of it.

 $ browser-tests open                # to run interactively
 $ browser-tests run                 # run headlessly
 $ browser-tests run --record --key  # record and upload a run
 $ browser-tests --vagrant run       # run if you use Vagrant
 $ browser-tests --wsl ..cypress run # run if you use WSL

You need to have installed cypress already using npm, and it needs to be on
your PATH.

=cut
