#!/usr/bin/perl

use strict;
use warnings;
use lib "/opt/pos/lib";
use FindBin qw( $Bin );
use Data::Dumper;
use App::POS::Config;
use Cache::Memcached::Fast;
use JSON::XS qw( encode_json decode_json );
use MIME::Base64 qw( decode_base64 encode_base64 );
use File::Slurp qw( write_file read_dir read_file );
use Digest::MD5 qw( md5_hex );
use Spreadsheet::ParseExcel::Simple;
use App::POS::Machine::Server;
use App::POS::Utils qw(
  post software_restart stations_software_restart random_sleep
  translate fix_encoding
);

# this was built to be used with the cloud db_exporter version (PHC and stuff)

my $URL = 'http://apisv2.net-bo.com/cloud_pos_data';

my $SLEEP_INTERVAL = [ 240 .. 300 ];
my $GROUP = "NOVOS";

my $cfg = App::POS::Config->new( file => "/opt/pos/etc/config.ini" );
my $info = $cfg->load_fixed();
my $SAFT_TaxRegistratioNumber = $info->{SAFT_TaxRegistratioNumber} || "";
my $SAFT_BusinessName = $info->{SAFT_BusinessName} || "";

my $machine = undef;

if (exists $info->{STATION_NUMBER} && $info->{STATION_NUMBER} == 0) {
  $machine = App::POS::Machine::Server->new( config => $cfg );
} else {
  die "Needs to be executed in the server machine...\n";
}

$info->{INTERNET_SYNC_TOKEN} //= "";
my $token = $info->{INTERNET_SYNC_TOKEN};
die "Check INTERNET_SYNC_TOKEN param...\n" unless $token;

$info->{INTERNET_SYNC_UPDATE_PCU} //= 0;
$info->{INTERNET_SYNC_UPDATE_STOCK} //= 0;

my $nodaemon = shift;

my $db = $machine->database();
my $dbh = $db->dbh();

my $machine_id = `/opt/pos/common/bin/machine_id`;
my $ret;

# register this hardlock in the server
$ret = post(
  "$URL/hardlock_register", {
    token => $token,
    hardlock => $machine_id,
    store_nif => $SAFT_TaxRegistratioNumber,
    store_name => $SAFT_BusinessName,
  }
);

eval { $ret = decode_json($ret) };
die "Error registering hardlock on server...\n" unless exists $ret->{success};

while(1) {
  my $sleep = random_sleep($SLEEP_INTERVAL);

  # check if hardlock needs sync
  $ret = post(
    "$URL/hardlock_needs_sync",
    { token => $token, hardlock => $machine_id }
  );

  eval { $ret = decode_json($ret) };

  if (! exists $ret->{sync}) {
    print STDERR "Will sleep - $sleep\n";
    sleep $sleep;
    next;
  }

  # needs sync...
  if ($ret->{sync}) {
    print STDERR "Needs sync!!\n";

    # get data from server
    $ret = post(
      "$URL/db_exporter_data",
      { token => $token, hardlock => $machine_id }
    );

    eval { $ret = decode_json($ret) };
    $ret->{success} //= 0;

    if ($ret->{success}) {
      import_data($db, $ret);
    }

    # mark hardlock as synced
    $ret = post(
      "$URL/hardlock_synced",
      { token => $token, hardlock => $machine_id }
    );
  }

  if (defined $nodaemon) {
    exit;
  }

  print STDERR "Will sleep - $sleep\n";
  sleep $sleep;
}

sub import_data {
  my( $db, $data ) = @_;

  $data->{products} ||= [];
  $data->{special_prices} ||= [];
  $data->{special_prices_products} ||= [];

  print STDERR "> import_data()\n";
  print STDERR "  > products: ".scalar(@{$data->{products}})."\n";
  print STDERR "  > special_prices: ".scalar(@{$data->{special_prices}})."\n";
  print STDERR "  > special_prices_products: ".scalar(@{$data->{special_prices_products}})."\n";

  if (scalar @{$data->{products}}) {
    _products($db, $data->{products});
  }

  if (scalar @{$data->{special_prices}} && scalar @{$data->{special_prices_products}}) {
    _special_prices($db, $data->{special_prices}, $data->{special_prices_products});
  }
}

sub _products {
  my( $db, $p ) = @_;

  print STDERR "> _products()\n";

  my $c = 0;

  my %cur_md5 = ();
  my %cur = ();
  my %did_ids = ();
  my $inserted = 0;
  my $updated = 0;

  my $group_id = _create_group_dept($db, $GROUP);

  my $recs = $db->select("SELECT md5, id FROM products");

  for (@$recs) {
    $cur{$_->{id}} = 1;
    $_->{md5} //= "";
    next unless $_->{md5};
    $cur_md5{$_->{id}} = $_->{md5};
  }

  #print STDERR Dumper(\%cur);
  #print STDERR Dumper(\%cur_md5);

  my $dbh = $db->dbh();

  my $sql_insert_pcu = ", cost_price";
  my $sql_insert_pcu_params = ", ?";
  my $sql_update_pcu = ", cost_price = ?";

  my $sql_insert_stock_aux = ", stock_current";
  my $sql_insert_stock_aux_params = ", ?";
  my $sql_update_stock_aux = ", stock_current = ?";

  if (! $info->{INTERNET_SYNC_UPDATE_PCU}) {
    $sql_insert_pcu = "";
    $sql_insert_pcu_params = "";
    $sql_update_pcu = "";
  }

  if (! $info->{INTERNET_SYNC_UPDATE_STOCK}) {
    $sql_insert_stock_aux = "";
    $sql_insert_stock_aux_params = "";
    $sql_update_stock_aux = "";
  }

  my $sth_insert = $db->dbh()->prepare(qq{
    INSERT INTO products (
      deleted, id, sell_price1, sell_price2, sell_price3, sell_price4,
      sell_price5, sell_vat1, sell_vat2, handy_plu, barcode, is_weight,
      ask_price, is_extra, priority, name, vat_isention, md5 $sql_insert_pcu $sql_insert_stock_aux
    ) VALUES (
      ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? $sql_insert_pcu_params $sql_insert_stock_aux_params
    )
  });

  my $sth_update = $db->dbh()->prepare(qq{
    UPDATE products
    SET deleted = ?, sell_price1 = ?, sell_price2 = ?, sell_price3 = ?,
      sell_price4 = ?, sell_price5 = ?, sell_vat1 = ?, sell_vat2 = ?,
      handy_plu = ?, barcode = ?, is_weight = ?, ask_price = ?, is_extra = ?,
      priority = ?, name = ?, vat_isention = ?, md5 = ? $sql_update_pcu $sql_update_stock_aux
    WHERE id = ?
  });

  foreach my $r (@$p) {
    next unless $r->{fo_id} =~ /^\d+$/;
    my $id = $r->{fo_id}*1;
    #my $id = $r->{id};

    $r->{name} //= "";
    next unless $r->{name};
    $r->{handy_plu} //= "";
    $r->{barcode} //= "";
    $r->{name} = fix_encoding($r->{name});
    $r->{vat_isention} ||= "";

    if ($r->{vat_isention}) {
      $r->{vat_isention} = fix_encoding($r->{vat_isention});
    }

    my( $insert, $update ) = ( 0, 0 );

    # new product
    if (! exists $cur{$id}) {
      $insert = 1;
      $cur_md5{$id} = _md5_gen($r);
    }
    # product exists, lets check if it changed
    else {
      # don't have md5, lets update it
      if (! exists $cur_md5{$id}) {
        $update = 1;
        $cur_md5{$id} = _md5_gen($r);
      }
      # it has md5, lets check if it changed
      else {
        my $md5 = _md5_gen($r);

        # something changed
        if ($md5 ne $cur_md5{$id}) {
          $update = 1;
          $cur_md5{$id} = $md5;
        }
        # nothing changed, ignore
        else {
          $did_ids{$id} = 1;
          next;
        }
      }
    }

    my $deleted = $r->{active} ? 0 : 1;

    if ($insert) {
      $did_ids{$id} = 1;

      $inserted++;

      my @params = (
        $deleted, $id, $r->{price1}, $r->{price2}, $r->{price3}, $r->{price4},
        $r->{price5}, $r->{vat1}, $r->{vat2}, $r->{handy_plu}, $r->{barcode},
        $r->{ask_weight}, $r->{ask_price}, $r->{is_extra}, $r->{priority},
        $r->{name}, $r->{vat_isention}, $cur_md5{$id},
      );

      if ($info->{INTERNET_SYNC_UPDATE_PCU}) {
        push @params, $r->{pcu};
      }

      if ($info->{INTERNET_SYNC_UPDATE_STOCK}) {
        push @params, $r->{stock};
      }

      $sth_insert->execute(@params);

      if ($group_id) {
        eval {
          $db->do(
            "INSERT INTO product_groups (product_id, group_id) VALUES (?, ?)",
            $id, $group_id,
          );
        };
      }
    }

    if ($update) {
      $did_ids{$id} = 1;

      $updated++;

      my @params = (
        $deleted, $r->{price1}, $r->{price2}, $r->{price3}, $r->{price4}, $r->{price5},
        $r->{vat1}, $r->{vat2}, $r->{handy_plu}, $r->{barcode}, $r->{ask_weight},
        $r->{ask_price}, $r->{is_extra}, $r->{priority}, $r->{name}, $r->{vat_isention},
        $cur_md5{$id},
      );

      if ($info->{INTERNET_SYNC_UPDATE_PCU}) {
        push @params, $r->{pcu};
      }

      if ($info->{INTERNET_SYNC_UPDATE_STOCK}) {
        push @params, $r->{stock};
      }

      push @params, $id;

      $sth_update->execute(@params);
    }

    #print STDERR "#### insert: $insert, update: $update\n";
    #print Dumper($r);
    #last if $c++ == 10;
  }

  if ($inserted || $updated) {
    my @did_ids = keys %did_ids;

    if (scalar @did_ids) {
      my $str = join(",", @did_ids);
      $db->do("UPDATE products SET deleted = 1 WHERE id NOT IN ($str)");
    }
  }

  print STDERR "products inserted: $inserted, updated: $updated\n";

  if ($inserted || $updated) {
    #print translate($machine, "INSIRA O DISPOSITIVO USB E PRIMA <ENTER>") . "\n";

    my $msg = translate($machine, "ATUALIZAÇÃO DE DADOS") . "<P>".
      "$inserted " . translate($machine, "PRODUTO(S) INSERIDO(S)") . "<BR>" .
      "$updated " . translate($machine, "PRODUTO(S) ATUALIZADO(S)");

    _send_alert_to_ui($msg);

    _refresh_products();
  }
}

sub _special_prices {
  my( $db, $sp, $spp ) = @_;

  print STDERR "> _special_prices()\n";

  $db->do("DELETE FROM special_prices_products");
  $db->do("DELETE FROM special_prices");

  foreach my $r (@$sp) {
    $db->do(qq{
      INSERT INTO special_prices (
        id, name, date_from, date_to, hour_from, hour_to
      ) VALUES (?, ?, ?, ?, ?, ?)
    }, $r->{id}, $r->{name}, $r->{date_from}, $r->{date_to}, $r->{hour_from},
      $r->{hour_to});
  }

  foreach my $r (@$spp) {
    $db->do(qq{
      INSERT INTO special_prices_products (
        special_price_id, product_id, price, discount
      ) VALUES (?, ?, ?, ?)
    }, $r->{special_price_id}, $r->{product_id}, $r->{price}, $r->{discount});
  }

  if (scalar @$sp) {
    _refresh_products();
  }
}

sub _md5_gen {
  my $h = shift;

  my $str = "";

  foreach my $k (sort keys %$h) {
    $str .= ($h->{$k} || "") ."#";
  }

  return md5_hex($str);
}

sub _create_group_dept {
  my( $db, $name ) = @_;

  my $dest_group_dept = $name;
  my $dept_id = 0;
  my $group_id = 0;

  my $sth;
  my $dbh = $db->dbh();

  $sth = $dbh->prepare(qq{
    SELECT f.id AS group_id, f.name AS group_name, sf.id AS dept_id, sf.name AS dept_name
    FROM groups sf
    LEFT JOIN groups f ON (sf.parent = f.id)
    WHERE sf.deleted = 0 AND f.deleted = 0 AND sf.name = ? AND f.name = ?
  });
  $sth->execute($dest_group_dept, $dest_group_dept);

  while (my $row = $sth->fetchrow_arrayref()) {
    $dept_id = $row->[2];
  }

  # no dept exists, lets create it
  if (! $dept_id) {
    $sth = $dbh->prepare(qq{
      UPDATE groups SET deleted = 1 WHERE name = ?
    });
    $sth->execute($dest_group_dept);

    my $new_group_id = "";

    $sth = $dbh->prepare("SELECT MAX(id)+1 FROM groups WHERE deleted = 0");
    $sth->execute();

    while (my $row = $sth->fetchrow_arrayref()) {
      $new_group_id = $row->[0];
    }

    $sth = $dbh->prepare("INSERT INTO groups (id, name, deleted, parent, priority) VALUES (?, ?, ?, ?, ?)");
    $sth->execute($new_group_id, $dest_group_dept, 0, 0, '-10000');

    $sth = $dbh->prepare(qq{
      SELECT id FROM groups WHERE name = ? AND deleted = 0 AND parent = 0
    });
    $sth->execute($dest_group_dept);

    my $group_id = 0;

    while (my $row = $sth->fetchrow_arrayref()) {
      $group_id = $row->[0];
    }

    if ($group_id) {
      my $new_dept_id = "";

      $sth = $dbh->prepare("SELECT MAX(id)+1 FROM groups WHERE deleted = 0");
      $sth->execute();

      while (my $row = $sth->fetchrow_arrayref()) {
        $new_dept_id = $row->[0];
      }

      $sth = $dbh->prepare("INSERT INTO groups (id, name, deleted, parent, priority) VALUES (?, ?, ?, ?, ?)");
      $sth->execute($new_dept_id, $dest_group_dept, 0, $group_id, '-10000');

      $sth = $dbh->prepare(qq{
        SELECT id FROM groups WHERE name = ? AND deleted = 0 AND parent = ?
      });
      $sth->execute($dest_group_dept, $group_id);

      while (my $row = $sth->fetchrow_arrayref()) {
        $dept_id = $row->[0];
      }
    }
  }

  return $dept_id;
}

sub _send_alert_to_ui {
  my $msg = shift;

  my $sock = IO::Socket::INET->new(
    PeerAddr => '127.0.0.1',
    PeerPort => '8088',
    Proto => 'tcp',
    Timeout => 5,
  );

  print $sock "#ALERT#$msg\n";

  $sock->close();
}

sub _refresh_products {
  my $sock = IO::Socket::INET->new(
    PeerAddr => '127.0.0.1',
    PeerPort => '8088',
    Proto => 'tcp',
    Timeout => 5,
  );

  print $sock "#PRODUCTSRELOAD#\n";

  $sock->close();
}
