package FS::part_export::soma;

use vars qw(@ISA %info %options $DEBUG);
use Tie::IxHash;
use FS::Record qw(fields dbh);
use FS::part_export;

@ISA = qw(FS::part_export);
$DEBUG = 1;

tie %options, 'Tie::IxHash',
  'url'         => { label => 'Soma OSS-API url', default=>'https://localhost:8088/ossapi/services' },
  'data_app_id' => { label => 'SOMA Data Application Id', default => '' },
;

my $notes = <<'EOT';
Real-time export of <b>svc_external</b> and <b>svc_broadband</b> record data
to SOMA Networks <a href="http://www.somanetworks.com">platform</a> via the
OSS-API.<br><br>

Freeside will attempt to create/delete a cpe for the ESN provided in
svc_external.  If a data application id is provided then freeside will
use the values provided in svc_broadband to manage the attributes and 
features of that cpe.

EOT

%info = (
  'svc'      => [ qw ( svc_broadband svc_external ) ],
  'desc'     => 'Real-time export to SOMA platform',
  'options'  => \%options,
  'nodomain' => 'Y',
  'notes'    => $notes,
);

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

  warn "_export_insert called for service ". $svc->svcnum
    if $DEBUG;

  my %args = ( url => $self->option('url'), method => '_queueable_insert' );

  $args{esn} = $self->esn($svc) or return 'No ESN found!';

  my $svcdb = $svc->cust_svc->part_svc->svcdb;
  $args{svcdb} = $svcdb;
  if ( $svcdb eq 'svc_external' ) {
    #do nothing
  } elsif ( $svcdb eq 'svc_broadband' ){
    $args{data_app_id} = $self->option('data_app_id')
  } else {
    return "Don't know how to provision $svcdb";
  }

  warn "dispatching statuschange" if $DEBUG;

  eval { statuschange(%args) };
  return $@ if $@;

  '';
}

sub _export_delete {
  my( $self, $svc ) = ( shift, shift );

  my %args = ( url => $self->option('url'), method => '_queueable_delete' );

  $args{esn} = $self->esn($svc) or return 'No ESN found!';

  my $svcdb = $svc->cust_svc->part_svc->svcdb;
  $args{svcdb} = $svcdb;
  if ( $svcdb eq 'svc_external' ) {
    #do nothing
  } elsif ( $svcdb eq 'svc_broadband' ){
    $args{data_app_id} = $self->option('data_app_id')
  } else {
    return "Don't know how to provision $svcdb";
  }

  eval { statuschange(%args) };
  return $@ if $@;

  '';
}

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

  my %args = ( url => $self->option('url'), method => '_queueable_replace' );

  $args{esn}     = $self->esn($old) or return 'No old ESN found!';
  $args{new_esn} = $self->esn($new) or return 'No new ESN found!';

  my $svcdb = $old->cust_svc->part_svc->svcdb;
  $args{svcdb} = $svcdb;
  if ( $svcdb eq 'svc_external' ) {
    #do nothing
  } elsif ( $svcdb eq 'svc_broadband' ){
    $args{data_app_id} = $self->option('data_app_id')
  } else {
    return "Don't know how to provision $svcdb";
  }

  eval { statuschange(%args) };
  return $@ if $@;

  '';
}

sub _export_suspend {
  my( $self, $svc ) = ( shift, shift );

  $self->queue_statuschange('_queueable_suspend', $svc);
}

sub _export_unsuspend {
  my( $self, $svc ) = ( shift, shift );

  $self->queue_statuschange('_queueable_unsuspend', $svc);
}

sub queue_statuschange {
  my( $self, $method, $svc ) = @_;

  my %args = ( url => $self->option('url'), method => $method );

  my $svcdb = $svc->cust_svc->part_svc->svcdb;
  $args{svcdb} = $svcdb;
  if ( $svcdb eq 'svc_external' ) {
    #do absolutely nothing
    return '';
  } elsif ( $svcdb eq 'svc_broadband' ){
    $args{data_app_id} = $self->option('data_app_id')
  } else {
    return "Don't know how to provision $svcdb";
  }

  $args{esn} = $self->esn($svc);

  my $queue = new FS::queue {
    'svcnum' => $svc->svcnum,
    'job'    => 'FS::part_export::soma::statuschange',
  };
  my $error = $queue->insert( %args );

  return $error if $error;

  '';

}

sub statuschange {  # subroutine
  my( %options ) = @_;

  warn "statuschange called with options ". 
       join (', ', map { "$_ => $options{$_}" } keys(%options))
    if $DEBUG;

  my $method = $options{method};

  eval "use Net::Soma 0.01 qw(ApplicationDef ApplicationInstance
                              AttributeDef AttributeInstance);";
  die $@ if $@;

  my %soma_objects = ();
  foreach my $service ( qw ( CPECollection CPEAccess AppCatalog Applications ) )
  {
    $soma_objects{$service} = new Net::Soma ( namespace => $service."Service",
                                              url       => $options{'url'},
                                              die_on_fault => 1,
                                            );
  }
  
  my $cpeid = eval {$soma_objects{CPECollection}->getCPEByESN( $options{esn} )};
  warn "failed to find CPE with ESN $options{esn}"
    if ($DEBUG && !$cpeid);

  if ( $method eq '_queueable_insert' && $options{svcdb} eq 'svc_external' ) {
    if ( !$cpeid ) {
      # only type 1 is used at this time
      $cpeid = $soma_objects{CPECollection}->createCPE( $options{esn}, 1 );
    } else {
      $soma_objects{CPECollection}->releaseCPE( $cpeid );
      die "Soma element for $options{esn} already exists";
    }
  }

  die "Can't find soma element for $options{esn}"
    unless $cpeid;

  warn "dispatching $method from statuschange" if $DEBUG;
  &{$method}( \%soma_objects, $cpeid, %options );

}

sub _queueable_insert {
  my( $soma_objects, $cpeid, %options ) = @_;

  warn "_queueable_insert called for $cpeid with options ". 
       join (', ', map { "$_ => $options{$_}" } keys(%options))
    if $DEBUG;

  my $appid = $options{data_app_id};
  if ($appid) {
    my $application =
      $soma_objects->{AppCatalog}
                   ->getDefaultApplicationInstance($appid, $cpeid);

    my $attribute =
      $soma_objects->{AppCatalog}
                   ->getDefaultApplicationAttributeInstance(2, 1, $cpeid);
    $attribute->value('G');

    my $i = 0;
    foreach my $instance (@{$application->attributes}) {
      unless ($instance->definitionId == $attribute->definitionId) {
        $i++; next;
      }
      $application->attributes->[$i] = $attribute;
      last;
    }

    $soma_objects->{Applications}->subscribeApp( $cpeid, $application );
  }

  $soma_objects->{CPECollection}->releaseCPE( $cpeid );

  '';
}

sub _queueable_delete {
  my( $soma_objects, $cpeid, %options ) = @_;

  my $appid = $options{data_app_id};
  my $norelease;

  if ($appid) {
    my $applications =
      $soma_objects->{Applications}->getSubscribedApplications( $cpeid );

    my $instance_id;
    foreach $application (@$applications) {
      next unless $application->definitionId == $appid;
      $instance_id = $application->instanceId;
    }

    $soma_objects->{Applications}->unsubscribeApp( $cpeid, $instance_id );

  } else {

    $soma_objects->{CPECollection}->deleteCPE($cpeid);
    $norelease = 1;

  }

  $soma_objects->{CPECollection}->releaseCPE( $cpeid ) unless $norelease;

  '';
}

sub _queueable_replace {
  my( $soma_objects, $cpeid, %options ) = @_;

  my $appid = $options{data_app_id} || '';

  if (exists($options{data_app_id})) {
    my $applications =
      $soma_objects->{Applications}->getSubscribedApplications( $cpeid );

    my $instance_id;
    foreach $application (@$applications) {
      next unless $application->internalName eq 'dataApplication';
      if ($application->definitionId != $options{data_app_id}) {
        $instance_id = $application->instanceId;
        $soma_objects->{Applications}->unsubscribeApp( $cpeid, $instance_id );
      }
    }

    if ($appid && !$instance_id ) {
      my $application =
        $soma_objects->{AppCatalog}
                     ->getDefaultApplicationInstance($appid, $cpeid);

      $soma_objects->{Applications}->subscribeApp( $cpeid, $application );
    }

  } else {

    $soma_objects->{CPEAccess}->switchCPE($cpeid, $options{new_esn})
      unless( $options{new_esn} eq $options{esn});

  }

  $soma_objects->{CPECollection}->releaseCPE( $cpeid );

  '';
}

sub _queueable_suspend {
  my( $soma_objects, $cpeid, %options ) = @_;

  my $appid = $options{data_app_id};

  if ($appid) {
    my $applications =
      $soma_objects->{Applications}->getSubscribedApplications( $cpeid );

    my $instance_id;
    foreach $application (@$applications) {
      next unless $application->definitionId == $appid;

      $instance_id = $application->instanceId;
      my $app_def =
        $soma_objects->{AppCatalog}->getApplicationDef($appid, $cpeid);
      my @attr_def = grep { $_->internalName eq 'status' }
                          @{$app_def->attributes};

      foreach my $attribute ( @{$application->attributes} ) {
        next unless $attribute->definitionId == $attr_def[0]->definitionId;
        $attribute->{value} = 'S';  

        $soma_objects->{Applications}->setAppAttribute( $cpeid,
                                                        $instance_id,
                                                        $attribute
                                                      );
      }
      
    }

  } else {

    #do nothing

  }

  $soma_objects->{CPECollection}->releaseCPE( $cpeid );

  '';
}

sub _queueable_unsuspend {
  my( $soma_objects, $cpeid, %options ) = @_;

  my $appid = $options{data_app_id};

  if ($appid) {
    my $applications =
      $soma_objects->{Applications}->getSubscribedApplications( $cpeid );

    my $instance_id;
    foreach $application (@$applications) {
      next unless $application->definitionId == $appid;

      $instance_id = $application->instanceId;
      my $app_def =
        $soma_objects->{AppCatalog}->getApplicationDef($appid, $cpeid);
      my @attr_def = grep { $_->internalName eq 'status' }
                     @{$app_def->attributes};

      foreach my $attribute ( @{$application->attributes} ) {
        next unless $attribute->definitionId == $attr_def[0]->definitionId;
        $attribute->{value} = 'E';  

        $soma_objects->{Applications}->setAppAttribute( $cpeid,
                                                        $instance_id,
                                                        $attribute
                                                      );
      }
      
    }

  } else {

    #do nothing

  }

  $soma_objects->{CPECollection}->releaseCPE( $cpeid );

  '';
}

sub esn {
  my ( $self, $svc ) = @_;
  my $svcdb = $svc->cust_svc->part_svc->svcdb;

  if ($svcdb eq 'svc_external') {
    my $esn = $svc->title;
    $esn =~ /^\s*([\da-fA-F]{1,16})\s*$/ && ($esn = $1);
    return sprintf( '%016s', $esn );
  }
  
  my $cust_pkg = $svc->cust_svc->cust_pkg;
  return '' unless $cust_pkg;

  my @cust_svc = grep { $_->part_svc->svcdb eq 'svc_external' &&
                        scalar( $_->part_svc->part_export('soma') )
                      }
                 $cust_pkg->cust_svc;
  return '' unless scalar(@cust_svc);
  warn "part_export::soma found multiple ESNs for cust_svc ". $svc->svcnum
    if scalar( @cust_svc ) > 1;

  my $esn = $cust_svc[0]->svc_x->title;
  $esn =~ /^\s*([\da-fA-F]{1,16})\s*$/ && ($esn = $1);
  
  sprintf( '%016s', $esn );
}


1;
