package FS::part_export::ldap;

use vars qw(@ISA);
use FS::Record qw( dbh );
use FS::part_export;

@ISA = qw(FS::part_export);

sub rebless { shift; }

sub _export_insert {
  my($self, $svc_acct) = (shift, shift);

  #false laziness w/shellcommands.pm
  {
    no strict 'refs';
    ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields;
    ${$_} = $svc_acct->$_() foreach qw( domain );
    my $cust_pkg = $svc_acct->cust_svc->cust_pkg;
    if ( $cust_pkg ) {
      my $cust_main = $cust_pkg->cust_main;
      ${$_} = $cust_main->getfield($_) foreach qw(first last);
    }
  }
  $crypt_password = ''; #surpress "used only once" warnings
  $crypt_password = crypt( $svc_acct->_password,
                             $saltset[int(rand(64))].$saltset[int(rand(64))] );


  my %attrib = map    { /^\s*(\w+)\s+(.*\S)\s*$/; ( $1 => eval(qq("$2")) ); }
                 grep { /^\s*(\w+)\s+(.*\S)\s*$/ }
                   split("\n", $self->option('attributes'));

  if ( $self->option('radius') ) {
    foreach my $table (qw(reply check)) {
      my $method = "radius_$table";
      my %radius = $svc_acct->$method();
      foreach my $radius ( keys %radius ) {
        ( my $ldap = $radius ) =~ s/\-//g;
        $attrib{$ldap} = $radius{$radius};
      }
    }
  }

  my $err_or_queue = $self->ldap_queue( $svc_acct->svcnum, 'insert',
    #$svc_acct->username,
    %attrib );
  return $err_or_queue unless ref($err_or_queue);

  #groups with LDAP?
  #my @groups = $svc_acct->radius_groups;
  #if ( @groups ) {
  #  my $err_or_queue = $self->ldap_queue(
  #    $svc_acct->svcnum, 'usergroup_insert',
  #    $svc_acct->username, @groups );
  #  return $err_or_queue unless ref($err_or_queue);
  #}

  '';
}

sub _export_replace {
  my( $self, $new, $old ) = (shift, shift, shift);

  local $SIG{HUP} = 'IGNORE';
  local $SIG{INT} = 'IGNORE';
  local $SIG{QUIT} = 'IGNORE';
  local $SIG{TERM} = 'IGNORE';
  local $SIG{TSTP} = 'IGNORE';
  local $SIG{PIPE} = 'IGNORE';

  return "can't (yet?) change username with ldap"
    if $old->username ne $new->username;

  return "ldap replace unimplemented";

  my $oldAutoCommit = $FS::UID::AutoCommit;
  local $FS::UID::AutoCommit = 0;
  my $dbh = dbh;

  my $jobnum = '';
  #if ( $old->username ne $new->username ) {
  #  my $err_or_queue = $self->ldap_queue( $new->svcnum, 'rename',
  #    $new->username, $old->username );
  #  unless ( ref($err_or_queue) ) {
  #    $dbh->rollback if $oldAutoCommit;
  #    return $err_or_queue;
  #  }
  #  $jobnum = $err_or_queue->jobnum;
  #}

  foreach my $table (qw(reply check)) {
    my $method = "radius_$table";
    my %new = $new->$method();
    my %old = $old->$method();
    if ( grep { !exists $old{$_} #new attributes
                || $new{$_} ne $old{$_} #changed
              } keys %new
    ) {
      my $err_or_queue = $self->ldap_queue( $new->svcnum, 'insert',
        $table, $new->username, %new );
      unless ( ref($err_or_queue) ) {
        $dbh->rollback if $oldAutoCommit;
        return $err_or_queue;
      }
      if ( $jobnum ) {
        my $error = $err_or_queue->depend_insert( $jobnum );
        if ( $error ) {
          $dbh->rollback if $oldAutoCommit;
          return $error;
        }
      }
    }

    my @del = grep { !exists $new{$_} } keys %old;
    if ( @del ) {
      my $err_or_queue = $self->ldap_queue( $new->svcnum, 'attrib_delete',
        $table, $new->username, @del );
      unless ( ref($err_or_queue) ) {
        $dbh->rollback if $oldAutoCommit;
        return $err_or_queue;
      }
      if ( $jobnum ) {
        my $error = $err_or_queue->depend_insert( $jobnum );
        if ( $error ) {
          $dbh->rollback if $oldAutoCommit;
          return $error;
        }
      }
    }
  }

  # (sorta) false laziness with FS::svc_acct::replace
  my @oldgroups = @{$old->usergroup}; #uuuh
  my @newgroups = $new->radius_groups;
  my @delgroups = ();
  foreach my $oldgroup ( @oldgroups ) {
    if ( grep { $oldgroup eq $_ } @newgroups ) {
      @newgroups = grep { $oldgroup ne $_ } @newgroups;
      next;
    }
    push @delgroups, $oldgroup;
  }

  if ( @delgroups ) {
    my $err_or_queue = $self->ldap_queue( $new->svcnum, 'usergroup_delete',
      $new->username, @delgroups );
    unless ( ref($err_or_queue) ) {
      $dbh->rollback if $oldAutoCommit;
      return $err_or_queue;
    }
    if ( $jobnum ) {
      my $error = $err_or_queue->depend_insert( $jobnum );
      if ( $error ) {
        $dbh->rollback if $oldAutoCommit;
        return $error;
      }
    }
  }

  if ( @newgroups ) {
    my $err_or_queue = $self->ldap_queue( $new->svcnum, 'usergroup_insert',
      $new->username, @newgroups );
    unless ( ref($err_or_queue) ) {
      $dbh->rollback if $oldAutoCommit;
      return $err_or_queue;
    }
    if ( $jobnum ) {
      my $error = $err_or_queue->depend_insert( $jobnum );
      if ( $error ) {
        $dbh->rollback if $oldAutoCommit;
        return $error;
      }
    }
  }

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

  '';
}

sub _export_delete {
  my( $self, $svc_acct ) = (shift, shift);
  return "ldap delete unimplemented";
  my $err_or_queue = $self->ldap_queue( $svc_acct->svcnum, 'delete',
    $svc_acct->username );
  ref($err_or_queue) ? '' : $err_or_queue;
}

sub ldap_queue {
  my( $self, $svcnum, $method ) = (shift, shift, shift);
  my $queue = new FS::queue {
    'svcnum' => $svcnum,
    'job'    => "FS::part_export::ldap::ldap_$method",
  };
  $queue->insert(
    $self->machine,
    $self->option('dn'),
    $self->option('password'),
    @_,
  ) or $queue;
}

sub ldap_insert { #subroutine, not method
  my $ldap = ldap_connect(shift, (my $dn = shift), shift);
  my %attrib = @_;

  my $status = $ldap->add( $dn, attrs => [ %attrib ] );
  die $status->error if $status->is_error;

  $ldap->unbind;
}

#sub ldap_delete { #subroutine, not method
#  my $dbh = ldap_connect(shift, shift, shift);
#  my $username = shift;
#
#  foreach my $table (qw( radcheck radreply usergroup )) {
#    my $sth = $dbh->prepare( "DELETE FROM $table WHERE UserName = ?" );
#    $sth->execute($username)
#      or die "can't delete from $table table: ". $sth->errstr;
#  }
#  $dbh->disconnect;
#}

sub ldap_connect {
  my( $machine, $dn, $password ) = @_;
  my %bind_options;
  $bind_options{password} = $password if length($password);

  eval "use Net::LDAP";
  die $@ if $@;

  my $ldap = Net::LDAP->new($machine) or die $@;
  my $status = $ldap->bind( $dn, %bind_options );
  die $status->error if $status->is_error;

  $ldap;
}

