#!/usr/bin/perl -w

#script to move customers from one installation to another
# source is remote, destination is local
# script is kinda-specific to a somewhat old source installation (1.7? older?)
# target installation has to be 1.9 (after 9/2009)

use strict;
use vars qw( $sdbh );
use FS::DBI;
use FS::UID qw( adminsuidsetup dbh );
use FS::Schema qw( dbdef );
use FS::Record qw( qsearchs );
use FS::agent;
use FS::cust_main;
use FS::part_pkg;
use FS::part_svc;
use FS::cust_bill_ApplicationCommon;
use FS::svc_Common;
use FS::cust_event;
use FS::svc_domain;
use FS::cust_pkg;

my $DANGEROUS = 0;
my $DRY = 0;

#ssh -p 2222 -L 1080:66.209.32.4:7219 -L 5454:localhost:5432 66.209.32.4

#my $source_datasrc = 'DBI:Pg:host=66.209.32.4;dbname=freeside;sslmode=require';
my $source_datasrc = 'DBI:Pg:host=localhost;port=5454;dbname=freeside';
my $source_user = 'readonly';
my $source_pw = '';

#my @source_agents = ( 2, 7, 3, 4, 5, 1 );
my @source_agents = ( 1, 2, 3, 4, 5, 7 );

my $dest_agent_typenum = 12;

my $dest_refnum = 60;

my $dest_legacy_credit_reasontype = 5;

my $dest_pkg_classnum = 6;

my %domsvc_map = (
  1    => 20450,
  3653 => 20162,
  7634 => 20451,
);

#testing
#my %eventparts = (
#  'CARD' => [ 1, ],
#  'CHEK' => [],
#  'BILL' => [],
#  'DCHK' => [],
#  'DCRD' => [],
#  'COMP' => [],
#);
#production
my %eventparts = (
  'CARD' => [ 1, ],
  'CHEK' => [ 2, ],
  'BILL' => [ 5, ],
  'DCHK' => [ 12, ],
  'DCRD' => [ 15, ],
  'COMP' => [],
);

#--

# target(local) setup

my $user = shift
  or die "Usage:\n  (edit variables at top of script and then)\n".
         "  move-customers user\n";
adminsuidsetup $user;

$FS::cust_main::ignore_expired_card = 1;
$FS::cust_main::ignore_expired_card = 1;
$FS::part_pkg::skip_pkg_svc_hack = 1;
$FS::part_pkg::skip_pkg_svc_hack = 1;
$FS::cust_bill_ApplicationCommon::skip_apply_to_lineitems_hack = 1;
$FS::cust_bill_ApplicationCommon::skip_apply_to_lineitems_hack = 1;
$FS::svc_Common::noexport_hack = 1;
$FS::svc_Common::noexport_hack = 1;
$FS::svc_domain::whois_hack = 1;
$FS::svc_domain::whois_hack = 1;
$FS::cust_pkg::disable_agentcheck = 1;
$FS::cust_pkg::disable_agentcheck = 1;

my $void_paynum = 2147483646; #top of int range

# -- 

# source(remote) setup

$sdbh = FS::DBI->connect($source_datasrc, $source_user, $source_pw)
  or die $FS::DBI::errstr;

$sdbh->{ChopBlanks} = 1;

# --

my %map = ();
$map{'_DOMSVC'} = \%domsvc_map;

import_table('pkg_class', 'nomap' => 1);
import_table('svc_acct_pop', 'nomap' => 1);

#XXX
#import_table('reason_type', 'nomap' => 1);
#foreach my $src_typenum ( keys %{ $map{'reason_type'} } ) {
#  import_table('reason', 'reason_type' => $src_typenum,
#                         'search'      => 'reason_type',
#                         'map'         => 'reason_type',
#              );
#}

my $agent_sth = $sdbh->prepare(
  'SELECT * FROM agent WHERE agentnum IN ( '. join(',', @source_agents ). ')'
) or die $sdbh->errstr;

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


while ( my $agentrow = $agent_sth->fetchrow_hashref ) {

  my $src_agent = $agentrow->{'agent'};

  warn "importing customers for $src_agent\n";

  my $agent = qsearchs('agent', { 'agent' => $src_agent, 'disabled' => '' } );

  if ( $agent ) {

    warn "  using existing agentnum ". $agent->agentnum. "\n";

    if ( $DANGEROUS ) {
      warn "DELETING ALL CUSTOMERS OF $src_agent locally \n";

      foreach my $statement (
        'DELETE FROM cust_main WHERE agentnum = '. $agent->agentnum,
        ( map { "DELETE FROM $_
                   WHERE 0 = ( SELECT COUNT(*) FROM cust_main
                                 WHERE cust_main.custnum = $_.custnum )
                "
              }
              qw(
                  cust_credit
                  cust_main_invoice
                  cust_main_note
                  cust_pay
                  cust_refund
                )
        )
        #pkg_class, part_pkg_pop
        #part_pkg, pkg_svc, part_svc, part_svc_column
        #XXX more... does it matter?
      ) {

        #warn $statement;
        my $sth = dbh->prepare($statement) or die dbh->errstr;
        $sth->execute or die $sth->errstr;

      }

      dbh->commit or die dbh->errstr;

    }

  } else {

    warn "  creating new agent...\n";

    $agent = new FS::agent { 'agent' => $src_agent,
                             'typenum' => $dest_agent_typenum };
    my $error = $agent->insert;
    die $error if $error;

    warn "  agentnum ". $agent->agentnum. "\n";

  }

  $map{'agent'}->{ $agentrow->{'agentnum'} } = $agent->agentnum;

}

  #my $customer_sth = $sdbh->prepare(
  #  'SELECT * FROM cust_main WHERE agentnum = '. $agentrow->{'agentnum'}
  #) or die $sdbh->errstr;
my $customer_sth = $sdbh->prepare(
  'SELECT * FROM cust_main WHERE agentnum IN ( '. join(',', @source_agents ). ')
     ORDER BY custnum'
) or die $sdbh->errstr;

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

while ( my $customerrow = $customer_sth->fetchrow_hashref ) {

    #use Data::Dumper;
    # warn Dumper($customerrow);
    my $src_custnum = $customerrow->{'custnum'};

    warn "   $src_custnum has referral_custnum ". $customerrow->{'referral_custnum'}
      if $customerrow->{'referral_custnum'};

    my $cust_main = new FS::cust_main {
      %{ $customerrow },
      'custnum'      => '',
      'referral_custnum' => '', #restore afterwords?
      'refnum'       => $dest_refnum,
      'agentnum'     => $map{'agent'}->{ $customerrow->{'agentnum'} },
      'agent_custid' => $src_custnum,
    };

    #$cust_main->ship_country('') if $cust_main->ship_country eq '  ';
    #$cust_main->tax('') if $cust_main->tax =~ /^\s+$/;

    my $error = $cust_main->insert;
    if ( $error ) {
      warn "*** WARNING: error importing customer src custnum $src_custnum: $error";
      use Data::Dumper;
      warn Dumper($cust_main) if $src_custnum == 6854;
      next;
    }

    warn "inserting dest customer ". $cust_main->custnum. " for $src_custnum\n";

    $map{'cust_main'}->{$src_custnum} = $cust_main->custnum;

    #now import the relations, easy and hard:

    import_table( 'cust_main_note', 'custnum' => $src_custnum );

    import_table( 'cust_pay', 'custnum' => $src_custnum,
      #ivan showing up as cust_pay otaker
      # old db doesn't have cust_pay.otaker, pull it from history
      'preinsert_callback' => sub {
        my($row, $cust_pay) = @_;

        my $sth = $sdbh->prepare(
          "SELECT history_user FROM h_cust_pay WHERE history_action = 'insert'
             AND paynum = ". $row->{'paynum'}
        ) or die $sdbh->errstr;
        $sth->execute or die $sth->errstr;
        my $otaker = $sth->fetchrow_arrayref->[0];
        
        $cust_pay->otaker($otaker);
      },
    );

    # crap, cust_credit.reason is text in old db
#*** WARNING: error importing cust_credit src crednum 2200: failed to set reason for [ FS::cust_credit ]:  at ./move-customers line 232.
    import_table( 'cust_credit', 'custnum' => $src_custnum,
      'insert_opts' => [ 'reason_type' => $dest_legacy_credit_reasontype ],
      'preinsert_callback' => sub {
        my($row, $object) = @_;
        $object->set('reason', '(none)') if $object->get('reason') =~ /^\s*$/;
      },
    );

    import_table( 'cust_refund', 'custnum' => $src_custnum,
      'post_callback' => sub {
        #my( $src_refundnum, $dst_refundnum ) = @_;
        my $src_refundnum = shift;

        # cust_credit_refund (map refundnum and crednum...)
        import_table( 'cust_credit_refund',
                      'refundnum' => $src_refundnum,
                      'search'    => 'refundnum',
                      'map'       => 'cust_refund',
                      'map2'      => 'cust_credit',
                      'map2key'   => 'crednum',
                    );

        # cust_pay_refund (map refundnum and paynum...)
        import_table( 'cust_pay_refund',
                      'refundnum' => $src_refundnum,
                      'search'    => 'refundnum',
                      'map'       => 'cust_refund',
                      'map2'      => 'cust_pay',
                      'map2key'   => 'paynum',
                    );

      },
    );

    # dunno what's up with this (ship_country '  ', fixed)
#*** WARNING: error importing customer src custnum 6854: Illegal (name) (error code illegal_name) ship_last:  at ./move-customers line 129.

    # cust_pay_void
    import_table( 'cust_pay_void', 'custnum' => $src_custnum,
      'preinsert_callback' => sub {
        my($row, $object) = @_;
        $object->paynum( $void_paynum-- );
      },
    );

    # (not in old db: cust_attachment, cust_statement, cust_location,
    #  cust_main_exemption, cust_pay_pending )
    # (not used in old db: cust_pay_batch, cust_tax_exempt)
    # (not useful to migrate: queue)

    #werid direct cust_main relations: 

    # cust_pkg (part_pkg, part_svc, etc.)
    import_table( 'cust_pkg', 'custnum' => $src_custnum,
      'preinsert_callback' => sub {
        my($row, $object) = @_;
        my $src_pkgpart = $row->{'pkgpart'} or die "wtf";
        my $dest_pkgpart = $map{'part_pkg'}->{$src_pkgpart};
        if ( $dest_pkgpart ) {
          $object->pkgpart($dest_pkgpart);
          return;
        }

        my $sth = $sdbh->prepare(
          "SELECT * FROM part_pkg WHERE pkgpart = $src_pkgpart"
        ) or die $sdbh->errstr;

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

        my $part_pkg_row = $sth->fetchrow_hashref
          or die "cust_pkg.pkgpart missing in part_pkg?!";

        my $hashref = {
          %{ $part_pkg_row },
          'pkgpart'  => '',
        };
        my $src_classnum = $part_pkg_row->{'classnum'};
        $hashref->{'classnum'} = $map{'pkg_class'}->{ $src_classnum }
          if $src_classnum;

        my $part_pkg = new FS::part_pkg $hashref;

        #$part_pkg->setuptax('') if $part_pkg->setuptax =~ /^\s+$/;
        #$part_pkg->recurtax('') if $part_pkg->recurtax =~ /^\s+$/;

        my $error = $part_pkg->insert( 'options' => {} );
        die "*** FATAL: error importing part_pkg src pkgpart $src_pkgpart ".
            ": $error"
          if $error;

        $map{ 'part_pkg' }->{ $part_pkg_row->{'pkgpart'} } = $part_pkg->pkgpart;
        
        # part_pkg_option
        import_table( 'part_pkg_option',
                      'pkgpart' => $src_pkgpart,
                      'search' => 'pkgpart',
                      'map'    => 'part_pkg',
                    );
        
        my $osth = $sdbh->prepare(
          "SELECT * FROM part_pkg_option WHERE pkgpart = $src_pkgpart"
        ) or die $sdbh->errstr;

        # pkg_svc, part_svc, part_svc_column
        import_table( 'pkg_svc',
          'pkgpart' => $src_pkgpart,
          'search'  => 'pkgpart',
          'map'     => 'part_pkg',
          'preinsert_callback' => sub {

            my($row, $object) = @_;
            my $src_svcpart = $row->{'svcpart'} or die "wtf2";
            my $dest_svcpart = $map{'part_svc'}->{$src_svcpart};
            if ( $dest_svcpart ) {
              $object->svcpart($dest_svcpart);
              return;
            }

            my $sth = $sdbh->prepare(
              "SELECT * FROM part_svc WHERE svcpart = $src_svcpart"
            ) or die $sdbh->errstr;

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

            my $part_svc_row = $sth->fetchrow_hashref
              or die "svcpart missing in part_svc?!";

            my $hashref = {
              %{ $part_svc_row },
              'svcpart' => '',
            };

            my $part_svc = new FS::part_svc $hashref;
            $part_svc->disabled('') if $part_svc->disabled =~ /^\s+$/;
            my $error = $part_svc->insert;
            die "*** FATAL: error importing part_svc src svcpart $src_svcpart ".
                ": $error"
              if $error;

            $map{ 'part_svc' }->{ $part_svc_row->{'svcpart'} } = $part_svc->svcpart;

            # part_svc_column
            import_table( 'part_svc_column',
                          'svcpart' => $src_svcpart,
                          'search'  => 'svcpart',
                          'map'     => 'part_svc',
                          'preinsert_callback' => sub {
                            my($row, $object) = @_;
                            if ( $object->columnname eq 'domsvc' ) {
                               $object->columnvalue( $map{'_DOMSVC'}->{ $object->columnvalue } );
                            }
                          },
                        );
        
            #what we came here for in the first place
            $object->svcpart( $part_svc->svcpart );

          }
        );

        #what we came here for in the first place
        $object->pkgpart( $part_pkg->pkgpart );

      },

      'post_callback' => sub {
        #my( $src_pkgnum, $dst_pkgnum ) = @_;
        my $src_pkgnum = shift;

        #XXX grr... action makes this very hard... 
        ## cust_pkg_reason (shit, and bring in/remap reasons)
        #import_table( 'cust_pkg_reason',
        #                'pkgnum'  => $src_pkgnum,
        #                'search'  => 'pkgnum',
        #                'map'     => 'cust_pkg',
        #                'map2'    => 'reason',
        #                'map2key' => 'reasonnum',
        #            );

        #cust_svc
        import_table( 'cust_svc',
                        'pkgnum'  => $src_pkgnum,
                        'search'  => 'pkgnum',
                        'map'     => 'cust_pkg',
                        'map2'    => 'part_svc',
                        'map2key' => 'svcpart',
                        'post_callback' => sub {
                          #my( $src_svcnum, $dst_svcnum ) = @_;
                          my $src_svcnum = shift;

                          #svc_domain
                          import_table( 'svc_domain',
                                          'svcnum' => $src_svcnum,
                                          'search' => 'svcnum',
                                          'map'    => 'cust_svc',
                                          'noblank_primary' => 1,
                                      );

                          #svc_acct
                          import_table( 'svc_acct',
                                          'svcnum'  => $src_svcnum,
                                          'search'  => 'svcnum',
                                          'map'     => 'cust_svc',
                                          'noblank_primary' => 1,
                                          'map2'    => 'svc_acct_pop',
                                          'map2key' => 'popnum',
                                          #'map3'    => 'svc_domain',
                                          'map3'    => '_DOMSVC',
                                          'map3key' => 'domsvc',
                                      );

                          #radius_usergroup
                          import_table( 'radius_usergroup',
                                          'svcnum' => $src_svcnum,
                                          'search' => 'svcnum',
                                          'map'    => 'cust_svc',
                                      );

                          #other svc_ tables not in old db

                        },
                    );

      },




    );
    # end of cust_pkg (part_pkg, part_svc, etc.)

    # cust_bill (invnum move)
    import_table( 'cust_bill', 'custnum' => $src_custnum,
      'preinsert_callback' => sub {
        my($row, $object) = @_;
        $object->agent_invid( $row->{'invnum'} );
      },
      'post_callback' => sub {
        my( $src_invnum, $dst_invnum ) = @_;
        #my $src_invnum = shift;

        # cust_bill_pkg ( map invnum and pkgnum... )
        import_table( 'cust_bill_pkg',
                      'invnum' => $src_invnum,
                      'search'  => 'invnum',
                      'map'     => 'cust_bill',
                      'map2'    => 'cust_pkg',
                      'map2key' => 'pkgnum',
                      'post_callback' => sub {
                        my $src_billpkgnum = shift;

                        import_table( 'cust_bill_pkg_detail',
                                      'billpkgnum' => $src_billpkgnum,
                                      'search'    => 'billpkgnum',
                                      'map'       => 'cust_bill_pkg',
                                      'addl_from' => 'left join cust_bill_pkg using ( invnum, pkgnum )',
                                    );

                      },
                    );

        # cust_credit_bill (map invnum and crednum... )
        import_table( 'cust_credit_bill',
                      'invnum' => $src_invnum,
                      'search'  => 'invnum',
                      'map'     => 'cust_bill',
                      'map2'    => 'cust_credit',
                      'map2key' => 'crednum',
                      'post_callback' => sub {
                        my $src_creditbillnum = shift;
                        #map creditbillnum and billpkgnum
                        import_table( 'cust_credit_bill_pkg',
                                      'creditbillnum' => $src_creditbillnum,
                                      'search'    => 'creditbillnum',
                                      'map'       => 'cust_credit_bill',
                                      'map2'      => 'cust_bill_pkg',
                                      'map2key'   => 'billpkgnum',
                                    );

                      },
                    );

        # cust_bill_pay (map invnum and paynum...)
        import_table( 'cust_bill_pay',
                      'invnum' => $src_invnum,
                      'search'  => 'invnum',
                      'map'     => 'cust_bill',
                      'map2'    => 'cust_pay',
                      'map2key' => 'paynum',
                      'post_callback' => sub {
                        my $src_billpaynum = shift;
                        #map billpaynum and billpkgnum
                        import_table( 'cust_bill_pay_pkg',
                                      'billpaynum' => $src_billpaynum,
                                      'search'    => 'billpaynum',
                                      'map'       => 'cust_bill_pay',
                                      'map2'      => 'cust_bill_pkg',
                                      'map2key'   => 'billpkgnum',
                                    );
                      },
                    );

        #need to do something about events. mark initial stuff as done
        foreach my $eventpart ( @{ $eventparts{$cust_main->payby} } ) {

          my $cust_event = new FS::cust_event {
            'eventpart' => $eventpart,
            'tablenum'  => $dst_invnum,
            '_date'     => time, # XXX something?  probably not
            'status'    => 'done',
          };

          my $error = $cust_event->insert;
          die "*** FATAL: error inserting cust_event for eventpart $eventpart,".
              " tablenum (invnum) $dst_invnum: $error"
            if $error;

        }

      },
    );

    # ---

    # (not in old db: cust_pkg_detail)
    # (not used in old db: cust_bill_pay_batch, cust_pkg_option)

    # ---

    # (not in old db: cust_bill_pkg_display, cust_bill_pkg_tax_location,
    #  cust_bill_pkg_tax_rate_location, cust_tax_adjustment, cust_svc_option, )
    # (not used in old db: cust_tax_exempt_pkg)

    #do this last, so no notices go out
    import_table( 'cust_main_invoice', 'custnum' => $src_custnum );

    #dbh->commit or die dbh->errstr;
    warn "customer ". $cust_main->custnum. " inserted\n";
    #exit;

}


warn "import successful!\n";
if ( $DRY ) {
  warn "rolling back (dry run)\n";
  dbh->rollback or die dbh->errstr;
  warn "rolled back\n"
} else {
  warn "commiting\n";
  dbh->commit or die dbh->errstr;
  warn "committed\n";
}

sub import_table {
  my( $table, %opt ) = @_;

  eval "use FS::$table;";
  die $@ if $@;

  my $map = $opt{'map'} || 'cust_main';
  my $search = $opt{'search'} || 'custnum';

  $opt{'insert_opts'} ||= [];

  my $primary_key = dbdef->table($table)->primary_key;

  my $addl_from = defined($opt{'addl_from'}) ? $opt{'addl_from'} : '';

  my $sth = $sdbh->prepare(
    "SELECT * FROM $table $addl_from ".
    ( $opt{'nomap'} ? '' : " WHERE $search = ". $opt{$search} )
  ) or die $sdbh->errstr;

  $sth->execute or die "(searching $table): ". $sth->errstr;

  while ( my $row = $sth->fetchrow_hashref ) {
    #my $src_custnum = $customerrow->{'custnum'};

    my $hashref = { %$row };
    $hashref->{$primary_key} = ''
      unless $opt{'noblank_primary'};
    $hashref->{ $search } = $map{$map}->{ $row->{$search} }
      unless $opt{'nomap'};

    if ( $opt{'map2'} ) {
      my $key2 = $opt{'map2key'};
      $hashref->{$key2} = $map{ $opt{'map2'} }->{ $row->{$key2} }
        unless $opt{map2key} eq 'pkgnum' && (    $row->{$key2} eq '0'
                                              || $row->{$key2} eq '-1'
                                            )
            or ! defined($row->{$key2})
            or $row->{$key2} eq '';
      #warn "map $opt{map2}.$opt{map2key}: ". $row->{$key2}. " to ". $map{ $opt{'map2'} }->{ $row->{$key2} };
    }

    if ( $opt{'map3'} ) {
      my $key3 = $opt{'map3key'};
      $hashref->{$key3} = $map{ $opt{'map3'} }->{ $row->{$key3} };
    }

    my $object = eval "new FS::$table \$hashref;";
    die $@ if $@;

    &{ $opt{preinsert_callback} }( $row, $object )
      if $opt{preinsert_callback};

    my $error = $object->insert( @{ $opt{'insert_opts'} } );
    if ( $error ) {
      warn "*** WARNING: error importing $table src $primary_key ". $row->{$primary_key}. ": $error";
      next;
    }

    $map{ $table }->{ $row->{$primary_key} } = $object->get($primary_key);

    &{ $opt{post_callback} }( $row->{$primary_key}, $object->get($primary_key) )
      if $opt{post_callback};

  }

}

1;