#!/usr/bin/perl -Tw
use strict;
use DBI;
use HTML::TableParser;
use Date::Parse;
use Text::CSV_XS;
use FS::Record qw(qsearch qsearchs);
use FS::cust_credit;
use FS::cust_main;
use FS::cust_pkg;
use FS::cust_svc;
use FS::svc_acct;
use FS::part_referral;
use FS::part_pkg;
use FS::UID qw(adminsuidsetup);
my $DEBUG = 0;
my $dry_run = '0';
my $s_dbname = 'DBI:Pg:dbname=optigoldimport';
my $s_dbuser = 'freeside';
my $s_dbpass = '';
my $extension = '.htm';
#my $d_dbuser = 'freeside';
my $d_dbuser = 'enet';
#my $d_dbuser = 'ivan';
#my $d_dbuser = 'freesideimport';
my $radius_file    = 'radius.csv';
my $email_file    = 'email.csv';
#my $agentnum = 1;
my $agentnum = 13;
my $legacy_domain_svcnum = 1;
my $legacy_ppp_svcpart = 2;
my $legacy_email_svcpart = 3;
#my $legacy_broadband_svcpart = 4;
#my $legacy_broadband_svcpart = 14;
#my $previous_credit_reasonnum = 1;
my $previous_credit_reasonnum = 1220;
my $state = '';  #statemachine-ish
my $sourcefile;
my $s_dbh;
my $columncount;
my $rowcount;
my @args = (
             {
               id    => 1,
               hdr   => \&header,
               row   => \&row,
               start => \&start,
               end   => \&end,
             },
           );
$s_dbh = DBI->connect($s_dbname, $s_dbuser, $s_dbpass,
                           { 'AutoCommit' => 0,
                             'ChopBlanks' => 1,
                             'ShowErrorStatement' => 1
                           }
                     );
foreach ( qw ( billcycle cust email product ) ) {
  $sourcefile = $_;
  print "parsing $sourcefile\n";
  die "bad file name" unless $sourcefile =~ /^\w+$/;
  $columncount = 0;
  $rowcount = 0;
  my $c_sth = '';
  if ( $c_sth = $s_dbh->prepare("SELECT COUNT(*) FROM $sourcefile") ) {
    if ( $c_sth->execute ) {
      if ( $c_sth->fetchrow_arrayref->[0] ) {
        warn "already have data in $sourcefile table; skipping";
        next;
      }
    }
  }
  my $tp = new HTML::TableParser( \@args, { Decode => 1, Trim => 1, Chomp => 1 });
  $tp->parse_file($sourcefile.$extension) or die "failed";
  $s_dbh->commit or die $s_dbh->errstr;
#  $s_dbh->disconnect;
}
sub start {
  warn "start\n" if $DEBUG;
  my $table_id = shift;
  die "unexpected state change" unless $state eq '';
  die "unexpected table" unless $table_id eq '1';
  $state = 'table';
}
sub end {
  warn "end\n" if $DEBUG;
  my ($tbl_id, $line, $udata) = @_;
  die "unexpected state change in header" unless $state eq 'rows';
  die "unexpected table" unless $tbl_id eq '1';
  $state = '';
}
sub header {
  warn "header\n" if $DEBUG;
  my ($tbl_id, $line, $cols, $udata) = @_;
  die "unexpected state change in header" unless $state eq 'table';
  die "unexpected table" unless $tbl_id eq '1';
  $state = 'rows';
  die "invalid column ". join (', ', grep { !/^[ \w\r]+$/ } @$cols)
    if scalar(grep { !/^[ \w\r]+$/ } @$cols);
  my $sql = "CREATE TABLE $sourcefile ( ".
    join(', ', map { s/[ \r]/_/g; "$_ varchar NULL" } @$cols). " )";
  $s_dbh->do($sql) or die "create table failed: ". $s_dbh->errstr;
  $columncount = scalar( @$cols );
}
sub row {
  warn "row\n" if $DEBUG;
  my ($tbl_id, $line, $cols, $udata) = @_;
  die "unexpected state change in row" unless $state eq 'rows';
  die "unexpected table" unless $tbl_id eq '1';
  die "invalid number of columns: ". join(', ', @$cols)
    unless (scalar(@$cols) == $columncount);
  my $sql = "INSERT INTO $sourcefile VALUES(".
    join(', ', map { s/\s*(\S[\S ]*?)\s*$/$1/; $s_dbh->quote($_) } @$cols). ")";
  $s_dbh->do($sql) or die "insert failed: ". $s_dbh->errstr;
  $rowcount++;
  warn "row $rowcount\n" unless ($rowcount % 1000);
}
## now svc_acct from CSV files
$FS::cust_main::import=1;
$FS::cust_pkg::disable_agentcheck = 1;
$FS::cust_svc::ignore_quantity = 1;
my (%master_map) = ();
my (%referrals) = ();
my (%custid) = ();
my (%cancel) = ();
my (%susp) = ();
my (%adjo) = ();
my (%bill) = ();
my (%cust_pkg_map) = ();
my (%object_map) = ();
my (%package_cache) = ();
my $count = 0;
my $d_dbh = adminsuidsetup $d_dbuser;
local $FS::UID::AutoCommit = 0;
my @import = ( { 'file'     => $radius_file,
                 'sep_char' => ';',
                 'fields'   => [ qw( garbage1 username garbage2 garbage3 _password ) ],
                 'fixup'    => sub {
                                     my $hash = shift;
                                     delete $hash->{$_}
                                       foreach qw (garbage1 garbage2 garbage3);
                                     $hash->{'svcpart'} = $legacy_ppp_svcpart;
                                     $hash->{'domsvc'} = $legacy_domain_svcnum;
                                     '';
                                   },
                 'mapkey'   => 'legacy_ppp',
                 'skey'     => 'username',
               },
               { 'file'     => $email_file,
                 'sep_char' => ';',
                 'fields'   => [ qw( username null finger _password status garbage ) ],
                 'fixup'    => sub {
                                     my $hash = shift;
                                     #return 1
                                     #  if $object_map{'legacy_ppp'}{$hash->{'username'}};
                                     delete $hash->{$_}
                                       foreach qw (null status garbage);
                                     $hash->{'svcpart'} = $legacy_email_svcpart;
                                     $hash->{'domsvc'} = $legacy_domain_svcnum;
                                     '';
                                   },
                 'mapkey'   => 'legacy_email',
                 'skey'     => 'username',
               },
);
while ( @import ) {
  my $href = shift @import;
  my $file = $href->{'file'} or die "No file specified";
  my (@fields)   = @{$href->{'fields'}};
  my ($sep_char) = $href->{'sep_char'} || ';';
  my ($fixup)    = $href->{'fixup'};
  my ($mapkey)   = $href->{'mapkey'};
  my ($skey)     = $href->{'skey'};
  my $line;
  my $csv = new Text::CSV_XS({'sep_char' => $sep_char});
  open(FH, $file) or die "cannot open $file: $!";
  $count = 0;
  while ( defined($line=) ) {
    chomp $line;
    $line &= "\177" x length($line); # i hope this isn't really necessary
    $csv->parse($line)
      or die "cannot parse: " . $csv->error_input();
    my @values = $csv->fields();
    my %hash;
    foreach my $field (@fields) {
      $hash{$field} = shift @values;
    }
    if (@values) {
      warn "skipping malformed line: $line\n";
      next;
    }
    my $skip = &{$fixup}(\%hash)
      if $fixup;
    unless ($skip) {
      my $svc_acct = new FS::svc_acct { %hash };
      my $error = $svc_acct->insert;
      if ($error) {
        warn $error;
        next;
      }
      if ($skey && $mapkey) {
        my $key = (ref($skey) eq 'CODE') ? &{$skey}($svc_acct) : $hash{$skey};
        $object_map{$mapkey}{$key} = $svc_acct->svcnum;
      }
      $count++
    }
  }
  print "Imported $count service records\n";
}
sub pkg_freq {
  my ( $href ) = ( shift );
  my $once;
  $href->{'one_time_list'} =~ /^\s*(\S[\S ]*?)\s*$/ && ($once = $1);
  $once
    ? 0
    : int(eval "$href->{'months_credit'} + 0");
#   int(eval "$href->{'month_credit'} + 0");
}
sub account_id {
  my $href = shift;
  if ($href->{'slave_account_id'} =~ /^\s*(\S[\S ]*?)\s*$/) {
    "slave:$1";
  }else{
    my $l = $href->{cbilling_cycle_login};
    $l =~ /^\s*(\S[\S ]*?)\s*$/ && ($l = $1);
    $l;
  }
}
sub b_or {
  my ( $field, $hash ) = ( shift, shift );
  $field = 'billing_'. $field
    if $hash->{'billing_use'} eq 'Billing Address';
  $hash->{$field};
}
sub p_or {
  my ( $field, $hash ) = ( shift, shift );
  $field = 'billing_'. $field
    if $hash->{'billing_use'} eq 'Billing Address';
  my $ac = ( $hash->{$field. '_area_code'}
          && $hash->{$field. '_area_code'} =~ /^\d{3}$/ )
             ? $hash->{$field. '_area_code'}. '-'
             : '903-' # wtf?
  ;
  ( $hash->{$field} && $hash->{$field} =~ /^\d{3}-\d{4}$/)
    ? $ac. $hash->{$field}
    : '';
}
sub or_b {
  my ( $field, $hash ) = ( shift, shift );
  $hash->{'billing_use'} eq 'Billing Address' ? $hash->{$field} : '';
}
sub or_p {
  my ( $field, $hash ) = ( shift, shift );
  $hash->{'billing_use'} eq 'Billing Address' && $hash->{$field} =~ /^\d{3}-\d{4}$/
    ? ( $hash->{$field. '_area_code'} =~ /^\d{3}$/
        ? $hash->{$field. '_area_code'}. '-'
        : '903-'  # wtf?
      ). $hash->{$field}
    : '';
}
my %payby_map = ( ''              => 'BILL',
                  'None'          => 'BILL',
                  'Credit Card'   => 'CARD',
                  'Bank Debit'    => 'CHEK',
                  'Virtual Check' => 'CHEK',
);
sub payby {
  $payby_map{ shift->{billing_type} };
}
sub payinfo {
  my $hash = shift;
  my $payby = payby($hash);
  my $info;
  my $cc =
    $hash->{'credit_card_number_1'}.
    $hash->{'credit_card_number_2'}.
    $hash->{'credit_card_number_3'}.
    $hash->{'credit_card_number_4'};
  my $bank = 
    $hash->{'bank_account_number'}.
    '@'.
    $hash->{'bank_transit_number'};
  if ($payby eq 'CARD') {
    $info = $cc;
  }elsif ($payby eq 'CHEK') {
    $info = $bank;
  }elsif ($payby eq 'BILL') {
    $info = $hash->{'blanket_purchase_order_number'};
    $bank =~ s/[^\d\@]//g;
    $cc =~ s/\D//g;
    if ( $bank =~ /^\d+\@\d{9}/) {
      $info = $bank;
      $payby = 'DCHK';
    }
    if ( $cc =~ /^\d{13,16}/ ) {
      $info = $cc;
      $payby = 'DCRD';
    }
  }else{
    die "unexpected payby";
  }
  ($info, $payby);
}
sub ut_name_fixup {
  my ($object, $field) = (shift, shift);
  my $value = $object->getfield($field);
  $value =~ s/[^\w \,\.\-\']/ /g;
  $object->setfield($field, $value);
}
sub ut_text_fixup {
  my ($object, $field) = (shift, shift);
  my $value = $object->getfield($field);
  $value =~ s/[^\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]/ /g;
  $object->setfield($field, $value);
}
sub ut_state_fixup {
  my ($object, $field) = (shift, shift);
  my $value = $object->getfield($field);
  $value = 'TX' if $value eq 'TTX';
  $object->setfield($field, $value);
}
sub ut_zip_fixup {
  my ($object, $field) = (shift, shift);
  my $value = $object->getfield($field);
  $value =~ s/[^-\d]//g;
  $object->setfield($field, $value);
}
my @tables = (
part_pkg => { 'stable'  => 'product',
#part_pkg => { 'stable'  => 'billcycle',
              'mapping' =>
                { 'pkg'      => sub { my $href = shift;
                                      $href->{'description'}
                                        ? $href->{'description'}
                                        : $href->{'product_id'};
                                    },
                  'comment'  => 'product_id',
                  'freq'     => sub { pkg_freq(shift) },
                  'recur_fee'=> sub { my $href = shift;
                                      my $price = ( pkg_freq($href)
                                        ? $href->{'unit_price'}
                                        : 0
                                      );
                                      $price =~ s/[^\d.]//g;
                                      $price = 0 unless $price;
                                      sprintf("%.2f", $price);
                                    },
                  'setuptax' => sub { my $href = shift;
                                      $href->{'taxable'} ? '' : 'Y';
                                    },
                  'recurtax' => sub { my $href = shift;
                                      $href->{'taxable'} ? '' : 'Y';
                                    },
                  'plan'     => sub { 'flat' },
                  'disabled' => sub { 'Y' },
                  'pkg_svc'  => sub { my $href = shift;
                                      my $result = {};
                                      if (pkg_freq($href)){
                                        $result->{$legacy_ppp_svcpart} = 1;
                                        $result->{$legacy_email_svcpart} = 
                                            $href->{emails_allowed}
                                          if $href->{emails_allowed};
                                      }
                                    },
                  'primary_svc'=> sub { pkg_freq(shift)
                                          ? $legacy_ppp_svcpart
                                          : ''
                                        ;
                                      },
                },
              'fixup'   => sub { my $part_pkg = shift;
                                 my $row = shift;
                                 unless ($part_pkg->pkg =~ /^\s*(\S[\S ]*?)\s*$/) {
                                   warn "no pkg: ". $part_pkg->pkg. " for ". $row->{product_id};
                                   return 1;
                                 }
                                 unless ($part_pkg->comment =~ /^\s*(\S[\S ]*?)\s*$/) {
                                   warn "no comment: ". $part_pkg->comment. " for ". $row->{product_id};
                                   return 1;
                                 }
                                 return 1 if exists($package_cache{$1});
                                 $package_cache{$1} = $part_pkg;
                                 1;
                               },
              'wrapup'  => sub { foreach (keys %package_cache) {
                                   my $part_pkg = $package_cache{$_};
                                   my $options =
                                     { map { my $v = $part_pkg->$_;
                                             $part_pkg->$_('');
                                             ($_ => $v);
                                           }
                                       qw (setup_fee recur_fee)
                                     };
                                   my $error =
                                     $part_pkg->insert(options=>$options);
                                   die "Error inserting package: $error"
                                     if $error;
                                   $count++ unless $error;
                                 }
                               },
            },
part_referral => { 'stable'  => 'cust',
                   'mapping' =>
                     { 'agentnum' => sub { $agentnum },
                       'referral' => sub { my $r = shift->{'referred_from'};
                                           $referrals{$r} = 1;
                                         },
                     },
                   'fixup'   => sub { 1 },
                   'wrapup'  => sub { foreach (keys %referrals) {
                                        my $part_referral =
                                          new FS::part_referral( {
                                            'agentnum' => $agentnum,
                                            'referral' => $referrals{$_},
                                          } );
                                        my $error = $part_referral->insert;
                                        die "Error inserting referral: $error"
                                          if $error;
                                        $count++ unless $error;
                                        $referrals{$_} = $part_referral->refnum;
                                      }
                                    },
                 },
#svc_acct  => { 'stable'  => 'cust',
#               'mapping' =>
#                 { 'username'     => 'login',
#                   '_password'    => 'password',
#                   'svcpart'      => sub{ $legacy_ppp_svcpart },
#                   'domsvc'       => sub{ $legacy_domain_svcnum },
#                   'status'       => 'status',
#                 },
#               'fixup'   => sub { my $svc_acct = shift;
#                                  my $row = shift;
#                                  my $id = $row->{'master_account'}
#                                           ? 'slave:'. $row->{'customer_id'}
#                                           : $row->{'login'};
#                                  my $status = $svc_acct->status;
#                                  if ( $status ne 'Current'
#                                    && $status ne 'On Hold' )
#                                  {
#                                    $cancel{$id} =
#                                      str2time($row->{termination_date});
#                                    warn "not creating (cancelled) svc_acct for " .
#                                      $svc_acct->username. "\n";
#                                    return 1
#                                  }
#                                  $susp{$id} = str2time($row->{hold_date})
#                                    if $status eq 'On Hold';
#                                  $adjo{$id} = str2time($row->{hold_date})
#                                    if ( $status eq 'Current' &&
#                                         $row->{hold_date} );
#                                  $bill{$id} =
#                                    str2time($row->{expiration_date});
#                                  '';
#                                },
#               'skey'    => sub { my $svc_acct = shift;
#                                  my $row = shift;
#                                  my $id = $row->{'master_account'}
#                                    ? 'slave:'. $row->{'customer_id'}
#                                    : $row->{'login'};
#                                },
#             },
cust_main => { 'stable'  => 'cust',
               'mapping' =>
                 { 'agentnum'     => sub { $agentnum },
                   'agent_custid' => sub { my $id = shift->{'customer_number'};
                                           if (exists($custid{$id})) {
                                             $custid{$id}++;
                                             $id.  chr(64 + $custid{$id});
                                           }else{
                                             $custid{$id} = 0;
                                             $id;
                                           }
                                         },
                   'last'         => sub { b_or('last_name', shift) || ' ' },
                   'first'        => sub { b_or('first_name', shift)  || ' ' },
                   'stateid'      => 'drivers_license_number',
                   'signupdate'   => sub { str2time(shift->{'creation_date'}) },
                   'company'      => sub { b_or('company_name', shift) },
                   'address1'     => sub { b_or('address', shift) || ' ' },
                   'city'         => sub { b_or('city', shift) || 'Paris' },
                   'state'        => sub { uc(b_or('state', shift)) || 'TX' },
                   'zip'          => sub { b_or('zip_code', shift) || '75460' },
                   'country'      => sub { 'US' },
                   'daytime'      => sub { p_or('phone', shift) },
                   'night'        => sub { p_or('phone_alternate_1', shift) },
                   'fax'          => sub { p_or('fax', shift) },
                   'ship_last'    => sub { or_b('last_name', shift) },
                   'ship_first'   => sub { or_b('first_name', shift) },
                   'ship_company' => sub { or_b('company_name', shift) },
                   'ship_address1'=> sub { or_b('address', shift) },
                   'ship_city'    => sub { or_b('city', shift) },
                   'ship_state'   => sub { uc(or_b('state', shift)) },
                   'ship_zip'     => sub { or_b('zip_code', shift) },
                   'ship_daytime' => sub { or_p('phone', shift) },
                   'ship_fax'     => sub { or_p('fax', shift) },
                   'tax'          => sub { shift->{taxable} eq '' ? 'Y' : '' },
                   'refnum'       => sub { $referrals{shift->{'referred_from'}}
                                           || 1
                                         },
                 },
               'fixup'   => sub { my $cust_main = shift;
                                  my $row = shift;
                                  my ($master_account, $customer_id, $login) =
                                    ('', '', '');
                                  $row->{'master_account'} =~ /^\s*(\S[\S ]*?)\s*$/
                                    && ($master_account = $1);
                                  $row->{'customer_id'} =~ /^\s*(\S[\S ]*?)\s*$/
                                    && ($customer_id = $1);
                                  $row->{'login'} =~ /^\s*(\S[\S ]*?)\s*$/
                                    && ($login = $1);
                                  my ($first, $last, $company) =
                                    ('', '', '');
                                  $cust_main->first =~ /^\s*(\S[\S ]*?)\s*$/
                                    && ($first = $1);
                                  $cust_main->last =~ /^\s*(\S[\S ]*?)\s*$/
                                    && ($last = $1);
                                  $cust_main->company =~ /^\s*(\S[\S ]*?)\s*$/
                                    && ($company = $1);
                                  unless ($first || $last || $company) {
                                    warn "bogus entry: ". $row->{'login'};
                                    return 1;
                                  }
                                  my $id = $master_account
                                           ? 'slave:'. $customer_id
                                           : $login;
                                  #my $id = $login;
                                  my $status = $row->{status};
                                  my $cancelled = 0;
                                  if ( $status ne 'Current'
                                    && $status ne 'current'
                                    && $status ne 'On Hold' )
                                  {
                                    $cancelled = 1;
                                    $cancel{$login} =
                                      str2time($row->{termination_date});
                                  }
                                  $susp{$id} = str2time($row->{hold_date})
                                    if ($status eq 'On Hold' && !$cancelled);
                                  $adjo{$id} = str2time($row->{hold_date})
                                    if ( $status eq 'Current' && !$cancelled &&
                                         $row->{hold_date} );
                                  $bill{$id} =
                                      str2time($row->{expiration_date})
                                    if (!$cancelled);
                                  my $svcnum =
                                    $object_map{legacy_ppp}{$row->{'login'} };
                                  unless( $cancelled || $svcnum || $status eq 'Pn Hold' ) {
                                    warn "can't find svc_acct for legacy ppp ".
                                        $row->{'login'}, "\n";
                                  }
                                  $object_map{svc_acct}{$id} = $svcnum
                                    unless $cancelled;
                                  $master_map{$login} = $master_account
                                    if $master_account;
                                  return 1 if $master_account;
                                  $cust_main->ship_country('US')
                                    if $cust_main->has_ship_address;
                                  ut_name_fixup($cust_main, 'first');
                                  ut_name_fixup($cust_main, 'company');
                                  ut_name_fixup($cust_main, 'last');
                                  my ($info, $payby) = payinfo($row);
                                  $cust_main->payby($payby);
                                  $cust_main->payinfo($info);
                                  $cust_main->paycvv(
                                      $row->{'credit_card_cvv_number'}
                                  )
                                    if ($payby eq 'CARD' or $payby eq 'DCRD');
                                  $cust_main->paydate('20'.
                                      $row->{'credit_card_exp_date_2'}.  '-'.
                                      substr(
                                        $row->{'credit_card_exp_date_1'},
                                        0,
                                        2,
                                      ).
                                      '-01'
                                  )
                                    if ($payby eq 'CARD' or $payby eq 'DCRD');
                                  my $payname = '';
                                  $payname = $row->{'credit_card_name'}
                                    if ($payby eq 'CARD' or $payby eq 'DCRD');
                                  $payname = $row->{'bank_name'}
                                    if ($payby eq 'CHEK' or $payby eq 'DCHK');
                                  $cust_main->payname($payname);
                                  $cust_main->paytype(
                                      $row->{'bank_account_to_debit'}
                                        ? 'Personal '.
                                          $row->{bank_account_to_debit}
                                        : ''
                                  )
                                    if ($payby eq 'CHEK' or $payby eq 'DCHK');
                                  $cust_main->payby('BILL')
                                    if ($cust_main->payby eq 'CHEK' && 
                                        $cust_main->payinfo !~ /^\d+\@\d{9}$/);
                                  $cust_main->payby('BILL')
                                    if ($cust_main->payby eq 'CARD' && 
                                        $cust_main->payinfo =~ /^\s*$/);
                                  $cust_main->paydate('2037-12-01')
                                    if ($cust_main->payby eq 'BILL');
                                  ut_text_fixup($cust_main, 'address1');
                                  ut_state_fixup($cust_main, 'state');
                                  ut_zip_fixup($cust_main, 'zip');
                                  '';
                                },
               'skey'    => sub { my $object = shift;
                                  my $href = shift;
                                  my $balance = sprintf("%.2f",
                                                        $href->{balance_due});
                                  if ($balance < 0) {
                                    my $cust_credit = new FS::cust_credit({
                                      'custnum'   => $object->custnum,
                                      'amount'    => sprintf("%.2f", -$balance),
                                      'reasonnum' => $previous_credit_reasonnum,
                                    });
                                    my $error = $cust_credit->insert;
                                    warn "Error inserting credit for ",
                                         $href->{'login'}, " : $error\n"
                                      if $error;
                                  }elsif($balance > 0) {
                                    my $error = $object->charge(
                                                  $balance, "Prior balance",
                                                );
                                    warn "Error inserting balance charge for ",
                                         $href->{'login'}, " : $error\n"
                                      if $error;
                                  }
                                  $href->{'login'};
                                },
             },
#cust_main => { 'stable'  => 'cust',
#               'mapping' =>
#                 { 'referred_by' => sub { my $href = shift;
#                                          my $u = shift->{'login'};
#                                          my $cn = $href->{'customer_number'};
#
#                                          my $c = qsearch( 'cust_main', 
#                                                           { 'custnum' => $cn }
#                                          ) or die "can't fine customer $cn";
#
#                                          my $s = qsearch( 'svc_acct', 
#                                                           { 'username' => $u }
#                                                         ) or return '';
#
#                                          my $n = $s->cust_svc
#                                                    ->cust_pkg
#                                                    ->cust_main
#                                                    ->custnum;
#
#                                          $c->referral_custnum($n);
#                                          my $error = $c->replace;
#                                          die "error setting referral: $error"
#                                            if $error;
#                                          '';
#                                        },
#                 };
#               'fixup'   => sub { 1 },
#             },
cust_pkg  => { 'stable'  => 'billcycle',
               'mapping' =>
                 { 'custnum'     => sub { my $l = shift->{cbilling_cycle_login};
                                          $l =~ /^\s*(\S[\S ]*?)\s*$/ && ($l = $1);
                                          my $r = $object_map{'cust_main'}{$l};
                                          unless ($r) {
                                            my $m = $master_map{$l};
                                            $r = $object_map{'cust_main'}{$m}
                                              if $m;
                                          }
                                          $r;
                                        },
                   'pkgpart'     => sub { my $href = shift;
                                          my $p = $href->{product_id};
                                          $p =~ /^\s*(\S[\S ]*?)\s*$/ && ($p = $1);
                                          my $pkg = $package_cache{$p}
                                            if $package_cache{$p};
                                          
                                          my $month = '';
                                          $href->{month_credit} =~ /\s*(\S[\S ]*?)\s*$/ && ($month = $1);
                                          $month = int(eval "$month + 0");
                                          my $price = 0;
                                          $href->{unit_price} =~ /\s*(\S[\S ]*?)\s*$/ && ($price = $1);
                                          $price = eval "$price + 0";
                                          if ($pkg) {
                                            $pkg = ''
                                              unless $pkg->freq + 0 == $month;
                                            if ($pkg && ($pkg->freq + 0)) {
                                              my $recur = 0;
                                              $pkg->recur_fee =~ /\s*(\S[\S ]*?)\s*$/ && ($recur = $1);
                                              $recur = eval "$recur + 0";
                                              $pkg = ''
                                                unless $recur == $price;
                                            }
                                            if ($pkg) {
                                              $pkg = ''
                                                unless $pkg->setuptax
                                                  eq ($href->{taxable} ? '' : 'Y');
                                            }
                                          }
                                          unless ($pkg) {
                                            my $pkghref = { 'pkg' => ($href->{description} ? $href->{description} : $href->{product_id} ),
                                                            'comment' => $href->{product_id},
                                                            'freq' => $month,
                                                            'setuptax' => ($href->{'taxable'} ? '' : 'Y'),
                                                            'recurtax' => ($href->{'taxable'} ? '' : 'Y'),
                                                            'plan' => 'flat',
                                                            'disabled' => 'Y',
                                                          };
                                            my @pkgs = qsearch('part_pkg', $pkghref);
                                            my $recur = sprintf("%.2f", ($month ? $price : 0));
                                            for (@pkgs) {
                                              my %options = $_->options;
                                              if ($options{recur_fee} eq $recur) {
                                                $pkg = $_;
                                                last;
                                              }
                                            }
                                            $pkghref->{recur_fee} = $recur
                                              unless $pkg;
                                            my $pkg_svc = {};
                                            if ($month){
                                              $pkg_svc->{$legacy_ppp_svcpart} = 1;
                                              $pkg_svc->{$legacy_email_svcpart} = 
                                                  $href->{emails_allowed}
                                                if $href->{emails_allowed};
                                            }
                                            $pkghref->{pkg_svc} = $pkg_svc;
                                            $pkghref->{primary_svc}
                                              = ( $month 
                                                  ? $legacy_ppp_svcpart
                                                  : '');
                                            unless ($pkg) {
                                              $pkg = new FS::part_pkg $pkghref;
                                              my $options =
                                                { map { my $v = $pkg->$_;
                                                        $pkg->$_('');
                                                        ($_ => $v);
                                                      }
                                                  qw (setup_fee recur_fee)
                                                };
                                              my $error =
                                                $pkg->insert(options=>$options);
                                              if ($error) {
                                                warn "Error inserting pkg ".
                                                  join(", ", map{"$_ => ". $pkg->get($_)} fields $pkg).
                                                  ": $error\n";
                                                $pkg = '';
                                              }
                                            }
                                          }
                                          $pkg ? $pkg->pkgpart : '';
                                        },
                   'setup'       => sub { str2time(shift->{creation_date}) },
                   'bill'        => sub { $bill{account_id(shift)}
                                          #$bill{$href->{cbilling_cycle_login}};
                                        },
                   'susp'        => sub { $susp{account_id(shift)}
                                          #$susp{$href->{cbilling_cycle_login}};
                                        },
                   'adjo'        => sub { $adjo{account_id(shift)}
                                          #$adjo{$href->{cbilling_cycle_login}};
                                        },
                   'cancel'      => sub { $cancel{account_id(shift)}
                                          #$cancel{$href->{cbilling_cycle_login}};
                                        },
                 },
               'fixup'  => sub { my ($object, $row) = (shift,shift);
                                 unless ($object->custnum) {
                                   warn "can't find customer for ".
                                     $row->{cbilling_cycle_login}. "\n";
                                   return 1;
                                 }
                                 unless ($object->pkgpart) {
                                   warn "can't find package for ".
                                     $row->{product_id}. "\n";
                                   return 1;
                                 }
                                 '';
                               },
               'skey'   => sub { my $object = shift;
                                 my $href = shift;
                                 my $id = $href->{'billing_cycle_item_id'};
                                 $id =~ /^\s*(\S[\S ]*?)\s*$/ && ($id = $1);
                                 $cust_pkg_map{$id} = $object->pkgnum;
                                 account_id($href);
                               },
               'wrapup'   => sub { for my $id (keys %{$object_map{'cust_pkg'}}){
                                     my $cust_svc =
                                       qsearchs( 'cust_svc', { 'svcnum' =>
                                                 $object_map{'svc_acct'}{$id} }
                                       );
                                     unless ($cust_svc) {
                                       warn "can't find legacy ppp $id\n";
                                       next;
                                     }
                                     $cust_svc->
                                       pkgnum($object_map{'cust_pkg'}{$id});
                                     my $error = $cust_svc->replace;
                                     warn "error linking legacy ppp $id: $error\n"
                                       if $error;
                                   }
                                 },
             },
svc_acct  => { 'stable'  => 'email',
               'mapping' =>
                 { 'username'    => 'email_name',
                   '_password'   => 'password',
                   'svcpart'      => sub{ $legacy_email_svcpart },
                   'domsvc'       => sub{ $legacy_domain_svcnum },
                 },
#               'fixup'   => sub { my ($object, $row) = (shift,shift);
#                                  my ($sd,$sm,$sy) = split '/',
#                                                     $row->{shut_off_date}
#                                    if $row->{shut_off_date};
#                                  if ($sd && $sm && $sy) {
#                                    my ($cd, $cm, $cy) = (localtime)[3,4,5];
#                                    $cy += 1900; $cm++;
#                                    return 1 if $sy < $cy;
#                                    return 1 if ($sy == $cy && $sm < $cm);
#                                    return 1 if ($sy == $cy && $sm == $cm && $sd <= $cd);
#                                  }
#                                  return 1 if $object_map{'cust_main'}{$object->username};
#                                  '';
#                                },
               'fixup'   => sub { my ($object, $row) = (shift,shift);
                                  my ($sd,$sm,$sy) = split '/',
                                                     $row->{shut_off_date}
                                    if $row->{shut_off_date};
                                  if ($sd && $sm && $sy) {
                                    my ($cd, $cm, $cy) = (localtime)[3,4,5];
                                    $cy += 1900; $cm++;
                                    return 1 if $sy < $cy;
                                    return 1 if ($sy == $cy && $sm < $cm);
                                    return 1 if ($sy == $cy && $sm == $cm && $sd <= $cd);
                                  }
                                  #return 1 if $object_map{'cust_main'}{$object->username};
                                  my $email_name;
                                  $row->{email_name} =~ /^\s*(\S[\S ]*?)\s*$/
                                    && ($email_name = $1);
                                  my $svcnum =
                                      $object_map{legacy_email}{$email_name}
                                    if $email_name;
                                  unless( $svcnum ) {
                                    warn "can't find svc_acct for legacy email ".
                                      $row->{'email_name'}, "\n";
                                    return 1;
                                  }
                                  
                                  $object_map{svc_acct}{'email:'.$row->{'email_customer_id'}} = $svcnum;
                                  return 1;
                                },
#               'skey'    => sub { my $object = shift;
#                                  my $href = shift;
#                                  'email:'. $href->{'email_customer_id'};
#                                },
               'wrapup'   => sub { for my $id (keys %{$object_map{'svc_acct'}}){
                                     next unless $id =~ /^email:(\d+)/;
                                     my $custid = $1;
                                     my $cust_svc =
                                       qsearchs( 'cust_svc', { 'svcnum' =>
                                                 $object_map{'svc_acct'}{$id} }
                                       );
                                     unless ($cust_svc) {
                                       warn "can't find legacy email $id\n";
                                       next;
                                     }
                                     if ($cust_svc->pkgnum) {
                                       warn "service already linked for $id\n";
                                       next;
                                     }
                                     $cust_svc->
                                       pkgnum($cust_pkg_map{$custid});
                                     if ($cust_svc->pkgnum){
                                       my $error = $cust_svc->replace;
                                       warn "error linking legacy email $id: $error\n"
                                         if $error;
                                     }else{
                                       warn "can't find package for $id\n"
                                     }
                                   }
                                 },
             },
);
#my $s_dbh = DBI->connect($s_datasrc, $s_dbuser, $s_dbpass) or die $DBI::errstr;
while ( @tables ) {
  my ($table, $href) = (shift @tables, shift @tables);
  my $stable = $href->{'stable'} or die "No source table"; # good enough for now
  my (%mapping) = %{$href->{'mapping'}};
  my ($fixup)   = $href->{'fixup'};
  my ($wrapup)  = $href->{'wrapup'};
  my ($id)      = $href->{'id'};
  my ($skey)    = $href->{'skey'};
  #$d_dbh->do("delete from $table");
  my $s_sth = $s_dbh->prepare("select count(*) from $stable");
  $s_sth->execute or die $s_sth->errstr;
  my $rowcount = $s_sth->fetchrow_arrayref->[0];
  $s_sth = $s_dbh->prepare("select * from $stable");
  $s_sth->execute or die $s_sth->errstr;
  my $row;
  $count = 0;
  while ( $row = $s_sth->fetchrow_hashref ) {
    my $class = "FS::$table";
    warn sprintf("%.2f", 100*$count/$rowcount). "% of $table processed\n"
      unless( !$count || $count % 100 );
    my $object = new $class ( {
        map { $_  => ( ref($mapping{$_}) eq 'CODE'
                       ? &{$mapping{$_}}($row)
                       : $row->{$mapping{$_}}
                     )
            }
          keys(%mapping) 
    } );
    my $skip = &{$fixup}($object, $row)
      if $fixup;
    unless ($skip) {
      my $error = $object->insert;
      if ($error) {
        warn "Error inserting $table ".
          join(", ", map{"$_ => ". $object->get($_)} fields $object).
          ": $error\n";
        next;
      }
      if ($skey) {
        my $key = (ref($skey) eq 'CODE') ? &{$skey}($object, $row)
                                         : $row->{$skey};
        $object_map{$table}{$key} = $object->get($object->primary_key)
      }
      $count++;
    }
  }
  &{$wrapup}()
    if $wrapup;
  print "$count/$rowcount of $table SUCCESSFULLY processed\n";
}
# link to any uncancelled package on customer
foreach my $username ( keys %{$object_map{'legacy_email'}} ) {
  my $cust_svc = qsearchs( 'cust_svc',
                           { 'svcnum' => $object_map{legacy_email}{$username} }
                         );
 next unless $cust_svc;
 next if $cust_svc->pkgnum;
 my $custnum = $object_map{cust_main}{$username};
 unless ($custnum) {
   my $master = $master_map{$username};
   $custnum = $object_map{'cust_main'}{$master}
     if $master;
   next unless $custnum;  
 }
 #my $extra_sql = ' AND 0 != (select freq from part_pkg where '.
 #                'cust_pkg.pkgpart = part_pkg.pkgpart )';
 my $extra_sql = " AND 'Prior balance' != (select pkg from part_pkg where ".
                 "cust_pkg.pkgpart = part_pkg.pkgpart )";
 my @cust_pkg = qsearch( {
                           'table'   => 'cust_pkg',
                           'hashref' => { 'custnum' => $custnum,
                                          'cancel'  => '',
                                        },
                           'extra_sql' => $extra_sql,
                       } );
 next unless scalar(@cust_pkg);
 $cust_svc->pkgnum($cust_pkg[0]->pkgnum);
 $cust_svc->replace;
}
if ($dry_run) {
  $d_dbh->rollback;
}else{
  $d_dbh->commit or die $d_dbh->errstr;
}