#!/usr/bin/env perl

use v5.14;
use warnings;
# avoid wide character warnings
no warnings 'utf8';

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

use FixMyStreet::DB;
use Getopt::Long::Descriptive;
use JSON::MaybeXS;
use Path::Tiny;

my ($opt, $usage) = describe_options(
    '%c',
    [ 'name=s', "Name of body" ],
    [ 'import=s', "File to import" ],
    [ 'commit', "Actually commit changes to the database" ],
    [ 'update-templates', "Update existing templates (default is to skip)" ],
    [ 'categories=s', "pipe-separated list of categories to export contacts for and filter templates by" ],
    [ 'all-categories', "Use instead of --categories to export all contacts & templates" ],
    [ 'help', "print usage message and exit", { shortcircuit => 1 } ],
);
print($usage->text), exit if $opt->help;
$usage->die unless $opt->name;
die "Please specify a file to import\n" if $opt->import && (! -e $opt->import || -d $opt->import);

my $J = JSON::MaybeXS->new(utf8 => 1, pretty => 1, canonical => 1);
my $body = FixMyStreet::DB->resultset("Body")->find({ name => $opt->name }) or die "Cannot find body " . $opt->name . "\n";
my @categories = split(/\|/, ($opt->{categories} || ''));

if ($opt->import) {
    import($opt->import, $opt->update_templates);
} else {
    export();
}

sub export {
    my %out;

    if (@categories || $opt->all_categories) {
        my @contacts = $body->contacts->search({
            $opt->all_categories ? () : ( category => { -in => \@categories } ),
        }, {
            order_by => 'category',
        })->all;
        die "Categories mismatch" unless (scalar @categories == scalar @contacts) || $opt->all_categories;
        for (@contacts) {
            push @{$out{contacts}}, {
                category => $_->category,
                email => $_->email,
                state => $_->state,
                non_public => $_->non_public ? JSON->true : JSON->false,
                extra => $_->extra,
            };
        }
    }

    my $templates = $body->response_templates->order_by('title');
    if (@categories) {
        $templates = $templates->search({
            'contact.category' => { -in => \@categories }
        }, {
            join => { 'contact_response_templates' => 'contact' }
        });
    }
    for ($templates->all) {
        push @{$out{templates}}, {
            title => $_->title,
            text => $_->text,
            email_text => $_->email_text,
            state => $_->state,
            categories => [ sort map { $_->category } $_->contacts->all ],
            auto_response => $_->auto_response,
            external_status_code => $_->external_status_code,
        };
    }

    for ($body->roles->order_by('name')->all) {
        push @{$out{roles}}, {
            permissions => $_->permissions,
            name => $_->name,
        };
    }

    for ($body->users->order_by('name')->all) {
        my $cats = $_->get_extra_metadata('categories');
        my @cat_names = sort map { $body->contacts->find({id => $_})->category } @$cats;
        push @{$out{users}}, {
            email => $_->email,
            name => $_->name,
            password => $_->password,
            roles => [ sort map { $_->name } $_->roles->all ],
            categories => \@cat_names,
            areas => $_->area_ids || [],
        };
    }

    print $J->encode(\%out);
}

sub import {
    my $file = shift;
    my $update_templates = shift;

    my $json = path($file)->slurp;
    my $out = $J->decode($json);

    my $db = FixMyStreet::DB->schema->storage;
    $db->txn_begin;

    foreach (@{$out->{contacts}}) {
        my $existing = $body->contacts->search({ category => $_->{category} })->single;
        if ($existing) {
            warn "Category $_->{category} already exists, skipping";
            next;
        }
        my $contact = $body->contacts->new({
            note => "Imported from $file",
            editor => 'export-import-data',
            whenedited => \'current_timestamp',
            category => $_->{category},
            email => $_->{email},
            state => $_->{state},
            non_public => $_->{non_public},
            extra => $_->{extra},
        });
        $contact->insert;
    }

    foreach (@{$out->{templates}}) {
        my $existing = $body->response_templates->search({ title => $_->{title} })->single;
        if ($existing && !$update_templates) {
            warn "Template with title $_->{title} already exists, skipping";
            next;
        }
        my $template = $body->response_templates->update_or_new({
            title => $_->{title},
            text => $_->{text},
            email_text => $_->{email_text},
            state => $_->{state},
            auto_response => $_->{auto_response},
            external_status_code => $_->{external_status_code},
        });
        $template->insert unless $template->in_storage;
        foreach (@{$_->{categories}}) {
            my $contact = $body->contacts->find({ category => $_ });
            unless ( $contact ) {
                my $msg = "Cannot find category $_ for template " . $template->title . "\n";
                if ($update_templates) {
                    warn $msg;
                    next;
                } else {
                    die $msg;
                }
            }
            $template->contact_response_templates->find_or_create({
                contact_id => $contact->id,
            });
        }
    }

    for my $r (@{$out->{roles}}) {
        my $role = $body->roles->find_or_new({
            name => $r->{name},
            permissions => $r->{permissions},
        });
        if ($role->in_storage) {
            warn "Role $r->{role} already exists; skipping";
            next;
        }
        $role->insert;
    }

    for my $u (@{$out->{users}}) {
        my $user = FixMyStreet::DB->resultset("User")->find_or_new({ email => $u->{email}, email_verified => 1 });
        if ($user->in_storage) {
            warn "User $u->{email} already exists; skipping";
            next;
        }
        $user->from_body($body->id);
        $user->name($u->{name});
        $user->password($u->{password}, 1);
        $user->area_ids($u->{areas});

        $user->insert;

        foreach my $role (@{$u->{roles}}) {
            my $role = $body->roles->find({ name => $role }) or die "Couldn't find role $role for user $u->{email}\n";
            $user->user_roles->create({
                role_id => $role->id,
            });
        }

        my @cat_ids;
        for my $cat_name (@{$u->{categories}}) {
            my $cat = $body->contacts->find({ category => $cat_name }) or die "Couldn't find category $cat_name for user $u->{email}\n";
            push @cat_ids, $cat->id;
        }
        $user->set_extra_metadata('categories', \@cat_ids);
        $user->set_extra_metadata(last_password_change => time());
        $user->update;
    }

    if ($opt->commit) {
        $db->txn_commit;
    } else {
        $db->txn_rollback;
    }
}
