#!/usr/bin/perl -w

use strict;
use vars qw($opt_d $opt_s $opt_q $opt_v $opt_r);
use vars qw($DEBUG $DRY_RUN);
use Getopt::Std;
use DBIx::DBSchema 0.31; #0.39
use FS::UID qw(adminsuidsetup checkeuid datasrc driver_name);  #getsecrets);
use FS::CurrentUser;
use FS::Schema qw( dbdef dbdef_dist reload_dbdef );
use FS::Misc::prune qw(prune_applications);
use FS::Conf;
use FS::Record qw(qsearch);
use FS::Upgrade qw(upgrade_schema upgrade_config upgrade upgrade_sqlradius);

my $start = time;

die "Not running uid freeside!" unless checkeuid();

getopts("dqrs");

$DEBUG = !$opt_q;
#$DEBUG = $opt_v;

$DRY_RUN = $opt_d;

my $user = shift or die &usage;
$FS::CurrentUser::upgrade_hack = 1;
$FS::UID::callback_hack = 1;
my $dbh = adminsuidsetup($user);
$FS::UID::callback_hack = 0;

if ( driver_name =~ /^mysql/i ) { #until 0.39 is required above
  eval "use DBIx::DBSchema 0.39;";
  die $@ if $@;
}

#needs to match FS::Schema...
my $dbdef_file = "%%%FREESIDE_CONF%%%/dbdef.". datasrc;

dbdef_create($dbh, $dbdef_file);

delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
reload_dbdef($dbdef_file);

warn "Upgrade startup completed in ". (time-$start). " seconds\n"; # if $DEBUG;
$start = time;

#$DBIx::DBSchema::DEBUG = $DEBUG;
#$DBIx::DBSchema::Table::DEBUG = $DEBUG;
#$DBIx::DBSchema::Index::DEBUG = $DEBUG;

my @bugfix = ();

if (dbdef->table('cust_main')->column('agent_custid') && ! $opt_s) { 
  push @bugfix,
    "UPDATE cust_main SET agent_custid = NULL where agent_custid = ''";

  push @bugfix,
    "UPDATE h_cust_main SET agent_custid = NULL where agent_custid = ''"
      if (dbdef->table('h_cust_main')); 
}

if ( dbdef->table('cgp_rule_condition') &&
     dbdef->table('cgp_rule_condition')->column('condition') 
   )
{
  push @bugfix,
   "ALTER TABLE ${_}cgp_rule_condition RENAME COLUMN condition TO conditionname"
      for '', 'h_';

}

if ( dbdef->table('areacode') and
     dbdef->table('areacode')->primary_key eq 'code' )
{
  if ( driver_name =~ /^mysql/i ) {
    push @bugfix, 
      'ALTER TABLE areacode DROP PRIMARY KEY',
      'ALTER TABLE areacode ADD COLUMN (areanum int auto_increment primary key)';
  }
  else {
    push @bugfix, 'ALTER TABLE areacode DROP CONSTRAINT areacode_pkey';
  }
}

# RT required field flag
# for consistency with RT schema: mysql is in CamelCase,
# pg is in lowercase, and they use different data types.
my ($t, $creq, $cdis) = 
  map { driver_name =~ /^mysql/i ? $_ : lc($_) }
  ('CustomFields','Required','Disabled');

if ( dbdef->table($t) && 
     ! dbdef->table($t)->column($creq) ) {
  push @bugfix,
    "ALTER TABLE $t ADD COLUMN $creq ".
    dbdef->table($t)->column($cdis)->type .
    ' NOT NULL DEFAULT 0';
}

if ( $DRY_RUN ) {
  print
    join(";\n", @bugfix ). ";\n";
} elsif ( @bugfix ) {

  foreach my $statement ( @bugfix ) {
    warn "$statement\n";
    $dbh->do( $statement )
      or die "Error: ". $dbh->errstr. "\n executing: $statement";
  }

  upgrade_schema();

  dbdef_create($dbh, $dbdef_file);
  delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
  reload_dbdef($dbdef_file);

}

#you should have run fs-migrate-part_svc ages ago, when you upgraded
#from 1.3 to 1.4... if not, it needs to be hooked into -upgrade here or
#you'll lose all the part_svc settings it migrates to part_svc_column

my @statements = dbdef->sql_update_schema( dbdef_dist(datasrc),
                                           $dbh,
                                           { 'nullify_default' => 1, },
                                         );

@statements = 
  grep { $_ !~ /^CREATE +INDEX +h_queue/i } #useless, holds up queue insertion
       @statements;

unless ( driver_name =~ /^mysql/i ) {
  #not necessary under non-mysql, takes forever on big db
  @statements =
    grep { $_ !~ /^ *ALTER +TABLE +h_queue +ALTER +COLUMN +job +TYPE +varchar\(512\) *$/i }
         @statements;
}

if ( $DRY_RUN ) {
  print
    join(";\n", @statements ). ";\n";
  exit;
} else {
  foreach my $statement ( @statements ) {
    warn "$statement\n";
    $dbh->do( $statement )
      or die "Error: ". $dbh->errstr. "\n executing: $statement";
  }

#  warn "Pre-schema change upgrades completed in ". (time-$start). " seconds\n"; # if $DEBUG;
#  $start = time;

#  dbdef->update_schema( dbdef_dist(datasrc), $dbh );
}

warn "Schema upgrade completed in ". (time-$start). " seconds\n"; # if $DEBUG;
$start = time;

my $hashref = {};
$hashref->{dry_run} = 1 if $DRY_RUN;
$hashref->{debug} = 1 if $DEBUG && $DRY_RUN;
prune_applications($hashref) unless $opt_s;

warn "Application pruning completed in ". (time-$start). " seconds\n"; # if $DEBUG;
$start = time;

print "\n" if $DRY_RUN;

if ( $dbh->{Driver}->{Name} =~ /^mysql/i && ! $opt_s ) {

  foreach my $table (qw( svc_acct svc_phone )) {

    my $sth = $dbh->prepare(
      "SELECT COUNT(*) FROM duplicate_lock WHERE lockname = '$table'"
    ) or die $dbh->errstr;

    $sth->execute or die $sth->errstr;

    unless ( $sth->fetchrow_arrayref->[0] ) {

      $sth = $dbh->prepare(
        "INSERT INTO duplicate_lock ( lockname ) VALUES ( '$table' )"
      ) or die $dbh->errstr;

      $sth->execute or die $sth->errstr;

    }

  }

  warn "Duplication lock creation completed in ". (time-$start). " seconds\n"; # if $DEBUG;
  $start = time;

}

$dbh->commit or die $dbh->errstr;

dbdef_create($dbh, $dbdef_file);

$dbh->disconnect or die $dbh->errstr;

delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload
$FS::UID::AutoCommit = 0;
$FS::UID::callback_hack = 1;
$dbh = adminsuidsetup($user);
$FS::UID::callback_hack = 0;
unless ( $DRY_RUN || $opt_s ) {
  my $dir = "%%%FREESIDE_CONF%%%/conf.". datasrc;
  if (!scalar(qsearch('conf', {}))) {
    my $error = FS::Conf::init_config($dir);
    if ($error) {
      warn "CONFIGURATION UPGRADE FAILED\n";
      $dbh->rollback or die $dbh->errstr;
      die $error;
    }
  }
}
$dbh->commit or die $dbh->errstr;
$dbh->disconnect or die $dbh->errstr;

$FS::UID::AutoCommit = 1;

$dbh = adminsuidsetup($user);

warn "Re-initialization with updated schema completed in ". (time-$start). " seconds\n"; # if $DEBUG;
$start = time;

upgrade_config()
  unless $DRY_RUN || $opt_s;

$dbh->commit or die $dbh->errstr;

warn "Config updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
$start = time;

upgrade()
  unless $DRY_RUN || $opt_s;

$dbh->commit or die $dbh->errstr;

warn "Table updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
$start = time;

upgrade_sqlradius()
  unless $DRY_RUN || $opt_s || $opt_r;

warn "SQL RADIUS updates completed in ". (time-$start). " seconds\n"; # if $DEBUG;
$start = time;

$dbh->commit or die $dbh->errstr;
$dbh->disconnect or die $dbh->errstr;

warn "Final commit and disconnection completed in ". (time-$start). " seconds; upgrade done!\n"; # if $DEBUG;

###

sub dbdef_create { # reverse engineer the schema from the DB and save to file
  my( $dbh, $file ) = @_;
  my $dbdef = new_native DBIx::DBSchema $dbh;
  $dbdef->save($file);
}

sub usage {
  die "Usage:\n  freeside-upgrade [ -d ] [ -r ] [ -s ] [ -q | -v ] user\n"; 
}

=head1 NAME

freeside-upgrade - Upgrades database schema for new freeside verisons.

=head1 SYNOPSIS

  freeside-upgrade [ -d ] [ -r ] [ -s ] [ -q | -v ]

=head1 DESCRIPTION

Reads your existing database schema and updates it to match the current schema,
adding any columns or tables necessary.

Also performs other upgrade functions:

=over 4

=item Calls FS:: Misc::prune::prune_applications (probably unnecessary every upgrade, but simply won't find any records to change)

=item If necessary, moves your configuration information from the filesystem in /usr/local/etc/freeside/conf.<datasrc> to the database.

=back

  [ -d ]: Dry run; output SQL statements (to STDOUT) only, but do not execute
          them.

  [ -q ]: Run quietly.  This may become the default at some point.

  [ -r ]: Skip sqlradius updates.  Useful for occassions where the sqlradius
          databases may be inaccessible.

  [ -v ]: Run verbosely, sending debugging information to STDERR.  This is the
          current default.

  [ -s ]: Schema changes only.  Useful for Pg/slony slaves where the data
          changes will be replicated from the Pg/slony master.

=head1 SEE ALSO

=cut