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_3';
extends 'App::bif';

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

    # Loop backwards from the software version until we find the
    # current repository version
    my @steps;
    foreach my $i ( $self->DBVERSION .. 1 ) {
        push( @steps, $i );
        last if -e $repo->child( $self->dbfile($i) );
    }

    # Make sure the current version is up to date
    my $current = pop @steps;
    my ( $latest_major, $latest_minor ) = $self->minor_upgrade($current);

    if ( $self->opts->{force} and !@steps ) {
        print "Forcing upgrade\n";
        push( @steps, $self->DBVERSION );
    }

    # Upgrade to latest version, one major version at a time
    while ( my $next = pop @steps ) {
        my $method = sprintf( 'upgrade_%d_to_%d', $current, $next );
        ( $latest_major, $latest_minor ) = $self->$method;
        $current = $next;
    }

    if ($latest_major) {

        # This should be the first use of self->dbw and so should appear in
        # the updated DB

        $self->dbw->txn(
            sub {
                $self->end_change(
                    id            => $self->new_change,
                    action_format => 'upgrade',
                    message =>
                      "Upgraded repository to v$latest_major.$latest_minor",
                );

            }
        );
    }

    printf("Analyzing");
    $self->dbw->do('ANALYZE');
    printf("Checking UUIDs");
    $self->dispatch('App::bif::check');
    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( "Database upgraded (v%d.%d-v%d.%d)\n",
                    $major, $old, $major, $new );
            }
            else {
                printf( "Database remains at 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 {
            print "Deploying db-v$next\n";
            ( undef, $minor ) = $next_db->deploy($next);
            my $current_prepare = 'xprepare_changeset_v' . $current;
            my $current_sth     = $current_db->$current_prepare(
                with => 'src',
                as   => sq(
                    select   => 'c.id AS id',
                    from     => 'changes c',
                    order_by => 'c.id ASC',
                ),
            );
            $current_sth->execute;

            my $current_changeset = 'changeset_v' . $current;
            my $next_changeset    = 'insert_changeset_v' . $next;

            print "Migrating data\n";
            while ( my $changeset = $current_sth->$current_changeset ) {
                $next_db->$next_changeset($changeset);
            }

            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 @hub_defs = $current_db->xhashrefs(
                select => [qw/id default_repo_id/],
                from   => 'hubs',
            );

            foreach my $hub (@hub_defs) {
                $next_db->xdo(
                    update => 'hubs',
                    set    => { default_repo_id => $hub->{default_repo_id} },
                    where  => { id => $hub->{id} },
                );
            }
        }
    );

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

    die "next count ($next_count) != current count ($current_count)"
      unless $next_count == $current_count;

    $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_3 (2015-06-30)

=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.

