package App::bif::upgrade;
use strict;
use warnings;
use Bif::Mo;
use Bif::DBW;
use DBIx::ThinSQL qw/sq/;
use Log::Any '$log';
use Path::Tiny;

our $VERSION = '0.1.5_5';
extends 'App::bif';

sub run {
    my $self = shift;
    my $opts = $self->opts;
    my $repo = $self->repo;

    my $current_major;
    my $current_minor;
    my $next_major;
    my $next_minor;

    # Loop backwards from the software version until we find the
    # current repository version
    my @major;
    foreach my $i ( $self->DBVERSION .. 1 ) {
        if ( -e $repo->child( $self->dbfile($i) ) ) {
            $current_major = $i;
            if ( $opts->{force} ) {
                print "Forcing inplace upgrade\n";
                push( @major, $i );
            }
            last;
        }
        else {
            push( @major, $i );
        }
    }

    $self->err( 'NoCurrentVersion', 'no current version!' )
      unless $current_major;

    unless (@major) {
        $self->minor_upgrade($current_major);
        return $self->ok('Upgrade');
    }

    print "Current major version: $current_major\n";

    # Upgrade to latest version, one major version at a time
    while ( my $next = pop @major ) {
        my $method = sprintf( 'upgrade_%d_to_%d', $current_major, $next );

        # ( $latest_major, $latest_minor ) =
        $self->$method;
        $current_major = $next;
    }

   # This should be the first use of self->dbw and so should appear in
   # the updated DB
   #    if ($latest_major) {
   # TODO: Make this a work-buffer item
   #        $self->dbw->txn(
   #            sub {
   #                $self->end_change(
   #                    id            => $self->new_change,
   #                    action_format => 'upgrade',
   #                    message =>
   #                      "Upgraded repository to v$latest_major.$latest_minor",
   #                );
   #
   #            }
   #        );
   #    }

    $self->lprint("Analyzing");
    $self->dbw->do('ANALYZE');
    $self->dispatch('App::bif::check');
    return $self->ok('ForceUpgrade') if $opts->{force};
    return $self->ok('Upgrade');
}

sub minor_upgrade {
    my $self   = shift;
    my $major  = shift;
    my $dbfile = $self->repo->child( $self->dbfile($major) );
    my $db     = Bif::DBW->connect( 'dbi:SQLite:dbname=' . $dbfile,
        undef, undef, undef, $self->opts->{debug} );

    return $db->txn(
        sub {
            my ( $old, $new ) = $db->deploy($major);
            if ( $new > $old ) {
                printf( "Original database upgraded (v%d.%d-v%d.%d)\n",
                    $major, $old, $major, $new );
            }
            else {
                printf( "Original database v%d.%d\n", $major, $new );
            }
            return ( $major, $new );
        }
    );
}

sub upgrade_1_to_1 {
    my $self = shift;
    return $self->generic_upgrade( 1, 1 );
}

sub generic_upgrade {
    my $self    = shift;
    my $current = shift;
    my $next    = shift;

    die $@ unless eval "require Bif::DB::Plugin::ChangeIDv$current;";
    die $@ unless eval "require Bif::DB::Plugin::ChangeIDv$next;";

    my $current_file = $self->repo->child( $self->dbfile($current) );
    my $current_db   = Bif::DBW->connect( 'dbi:SQLite:dbname=' . $current_file,
        undef, undef, undef, $self->opts->{debug} );
    my $current_count = $current_db->xval(
        select => 'count(id)',
        from   => 'changes',
    );

    $log->debug( 'Current db file: ' . $current_file );
    $log->debug( 'Current # changes: ' . $current_count );

    my $next_file = $self->repo->child( $self->dbfile($next) . '.next' );
    unlink $next_file;

    my $next_db = Bif::DBW->connect( 'dbi:SQLite:dbname=' . $next_file,
        undef, undef, undef, $self->opts->{debug} );

    my $minor;
    $next_db->txn(
        sub {
            $self->lprint("Deploying new database v$next");
            ( undef, $minor ) = $next_db->deploy($next);
            $self->lprint("New database v$next.$minor\n");

            my $total = $current_db->xval(
                select => 'count(*)',
                from   => 'changes',
            );

            my $src = $current_db->xprepare(
                select   => 'c.id AS id',
                from     => 'changes c',
                order_by => 'c.id ASC',
            );
            $src->execute;

            my $i = 0;
            while ( defined( my $id = $src->val ) ) {
                $self->lprint( 'Importing change ' . $i . '/' . $total );
                my $changeset = $current_db->changeset_v1($id);
                $next_db->save_changeset_v1( $id, $changeset );
                $i++;
            }

            $self->lprint( 'Imported changes: ' . $total . "\n" );
            $self->lprint("Running fixups\n");

            my @bif = $current_db->xhashrefs(
                select   => '*',
                from     => 'bifkv',
                order_by => 'rowid',
            );

            foreach my $bif (@bif) {
                $next_db->xdo(
                    insert_into => 'bifkv',
                    values      => $bif,
                );
            }

            $next_db->xdo( delete_from => 'sqlite_sequence' );
            my @seq = $current_db->xhashrefs(
                select   => '*',
                from     => 'sqlite_sequence',
                order_by => 'rowid',
            );

            foreach my $seq (@seq) {
                $next_db->xdo(
                    insert_into => 'sqlite_sequence',
                    values      => $seq,
                );
            }

            my @project_defs = $current_db->xhashrefs(
                select => [qw/id default_hub_id local/],
                from   => 'projects',
            );

            foreach my $project (@project_defs) {
                $next_db->xdo(
                    update => 'projects',
                    set    => {
                        default_hub_id => $project->{default_hub_id},
                        local          => $project->{local}
                    },
                    where => { id => $project->{id} },
                );
            }

            my @work_defs = $current_db->xhashrefs(
                select => [qw/*/],
                from   => 'work_buffers',
            );

            foreach my $work (@work_defs) {
                $next_db->xdo(
                    insert_into => 'work_buffers',
                    values      => $work,
                );
            }

            my $next_count = $next_db->xval(
                select => 'count(id)',
                from   => 'changes',
            );

            return $self->err( 'CountMismatch',
                "next count (%d) != current count (%d)",
                $next_count, $current_count )
              unless $next_count == $current_count;

            $next_db->xdo(
                insert_into => 'func_merge_changes',
                values      => { merge => 1 },
            );

            $self->lprint("Running PRAGMA foreign_key_check\n");
            my $sth = $next_db->prepare('PRAGMA foreign_key_check');
            $sth->execute;
            if ( my @bad = $sth->arrayrefs ) {
                print $self->render_table( ' l  r  l  r ',
                    [qw/Parent ID Destination Key/], \@bad );
                return $self->err( 'ForeignKeyCheck',
                    'unsatisfied foreign keys' );
            }
        }
    );

    $next_db->disconnect;

    rename $next_file, $self->repo->child( $self->dbfile($next) );
    return ( $next, $minor );
}

1;
__END__

=head1 NAME

=for bif-doc #admin

bif-upgrade - upgrade a repository

=head1 VERSION

0.1.5_5 (2015-08-13)

=head1 SYNOPSIS

    bif upgrade [OPTIONS...]

=head1 DESCRIPTION

The B<bif-upgrade> command upgrades the current repository database to
match the running version of bif.

    bif upgrade
    # Database remains at v1.318
    # UUIDs ok UUIDs

After the upgrade is committed the L<bif-check> command is run to
ensure the respository information is consistent.  As an administration
command, B<bif-upgrade> is only shown in usage messages when the
C<--help> option is used.

=head1 ARGUMENTS & OPTIONS

=over

=item --force, -f

Force the upgrade to occur even if the repository database version
matches the bif software version. This is equivalent to exporting the
data, initializing a new repository and importing the data back in,
except that bif has no export/import commands.

=back

=head1 SEE ALSO

L<bif>(1)

=head1 AUTHOR

Mark Lawrence E<lt>nomad@null.netE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2013-2015 Mark Lawrence <nomad@null.net>

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

