// Node
import * as fs from 'fs';
import * as stream from 'stream';
import * as zlib from 'zlib';

// Shared libraries
import * as Util from '@dra2020/util';
import * as Context from '@dra2020/context';
import * as LogAbstract from '@dra2020/logabstract';
import * as LogClient from '@dra2020/logclient';
import * as FSM from '@dra2020/fsm';
import * as Storage from '@dra2020/storage';
import * as DB from '@dra2020/dbabstract';
import * as DT from '@dra2020/dra-types';

import * as DD from '../lib/all';

class FsmTest extends FSM.Fsm
{
  constructor(env: DD.EnvironmentEx)
  {
    super(env);
  }

  tick(): void
  {
    if (this.ready)
    {
      console.log('dynamodb test: exiting');
      process.exit(0);
    }
  }
}

class FsmLoudOp extends FSM.Fsm
{
  col: DB.DBCollection;
  op: any;
  fsm: any;

  constructor(op: any)
  {
    super(env);
    this.col = op.col as DB.DBCollection;
    this.op = op;
  }

  tick(): void
  {
    if (this.ready && this.isDependentError)
    {
      console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: failure`);
      this.setState(FSM.FSM_DONE);
    }
    else if (this.ready)
    {
      switch (this.state)
      {
        case FSM.FSM_STARTING:
          switch (this.op.op)
          {
            case 'find':
              this.fsm = db.createFind(this.col, this.op.q);
              break;
            case 'update':
              this.fsm = db.createUpdate(this.col, this.op.q, this.op.u);
              break;
            case 'unset':
              this.fsm = db.createUnset(this.col, this.op.q, this.op.u);
              break;
            case 'delete':
              this.fsm = db.createDelete(this.col, this.op.q);
              break;
            case 'query':
              this.fsm = db.createQuery(this.col, this.op.q);
              break;
          }
          this.waitOn(this.fsm);
          this.setState(FSM.FSM_PENDING);
          break;

        case FSM.FSM_PENDING:
          switch (this.op.op)
          {
            case 'find':
              if (this.fsm.result)
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: ${JSON.stringify(this.fsm.result)}`);
              else
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: (null)`);
              break;
            case 'delete':
            case 'unset':
            case 'update':
              console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: success`);
              break;
            case 'query':
              if (this.fsm.result)
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: ${JSON.stringify(this.fsm.result)}`);
              else
                console.log(`${this.op.op}: with query ${JSON.stringify(this.op.q)}: (null)`);
              break;
          }
          this.setState(FSM.FSM_DONE);
          break;
      }
    }
  }
}

class FsmStreamEater extends FSM.Fsm
{
  fsmArray: FSM.FsmArray;

  constructor(env: DD.EnvironmentEx, col: DD.DynamoCollection)
  {
    super(env);
    this.fsmArray = env.dbx.createStream(col);
  }

  tick(): void
  {
    if (this.ready && this.state == FSM.FSM_STARTING)
    {
      if (this.fsmArray.a.length > 0)
        console.log(`dbstream output: ${JSON.stringify(this.fsmArray.a)}`);
      this.fsmArray.reset();
      this.waitOn(this.fsmArray);
    }
  }
}

class FsmCleanup extends FSM.Fsm
{
  colName: string;
  col: DD.DynamoCollection;
  fsmAll: DB.DBQuery;
  nDelete: number;

  constructor(env: DD.EnvironmentEx, colName: string, col: DD.DynamoCollection)
  {
    super(env)
    this.colName = colName;
    this.col = col;
    this.nDelete = 0;
  }

  get env(): DD.EnvironmentEx { return this._env as DD.EnvironmentEx }

  tick(): void
  {
    if (this.ready && this.isDependentError)
      this.setState(FSM.FSM_ERROR);
    else if (this.ready)
    {
      switch (this.state)
      {
        case FSM.FSM_STARTING:
          this.fsmAll = this.env.dbx.createQuery(this.col, {});
          this.waitOn(this.fsmAll.fsmResult);
          this.setState(FSM.FSM_PENDING);
          break;

        case FSM.FSM_PENDING:
          this.fsmAll.result.forEach((e: any) => {
              this.waitOn(this.env.dbx.createDelete(this.col, e));
              this.nDelete++;
            });
          this.fsmAll.fsmResult.reset();
          if (this.fsmAll.done)
          {
            if (this.ready)
            {
              this.setState(FSM.FSM_DONE);
              console.log(`cleanup: ${this.colName}: deleted ${this.nDelete} items`);
            }
          }
          else
            this.waitOn(this.fsmAll.fsmResult);
          break;
      }
    }
  }
}

let env: DD.EnvironmentEx = { context: null, log: null, fsmManager: null, storageManager: null, dbx: null };
env.context = Context.create();
env.log = LogClient.create(env);
env.fsmManager = new FSM.FsmManager();
let tester = new FsmTest(env);
let db: DB.DBClient = new DD.DynamoClient(env);
tester.waitOn(db);

Object.keys(DT.Schemas).forEach((name: string) => {
    DT.Schemas[name].Collection = db.createCollection(`test-${name}`, DT.Schemas[name]);
    tester.waitOn(DT.Schemas[name].Collection);
  });

let users = DT.Schemas['users'].Collection;
tester.waitOn(users);
let maps = DT.Schemas['state'].Collection;
tester.waitOn(maps);

//let eater = new FsmStreamEater(env, maps);
//tester.waitOn(eater);

let x = Math.trunc(10000 * Math.random());
function uid(): string { return `map${x++}` }

let map1: string = uid();
let map2: string = uid();

let ops: any[] = [
  { col: users, op: 'update', q: { id: 'user1' }, u: { email: 'a@b', complex: { empty: '', full: 'stuff' } } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { email: 'a@b' } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { id: 'user1', email: 'a@b' } },
  { col: users, op: 'update', q: { id: 'user3' }, u: { email: 'x@y' } },
  { col: users, op: 'query', q: { } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { roles: [ 'insider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: users, op: 'find', q: { email: 'a@b' } },
  { col: users, op: 'find', q: { id: 'user2' } },
  { col: users, op: 'find', q: { email: 'c@d' } },
  { col: users, op: 'delete', q: { id: 'user1' } },
  { col: users, op: 'delete', q: { id: 'user1' } },
  { col: users, op: 'delete', q: { id: 'user2' } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { roles: [ 'insider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: users, op: 'update', q: { id: 'user1' }, u: { admin: true, roles: [ 'outsider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: users, op: 'unset', q: { id: 'user1' }, u: { admin: false, roles: [ 'outsider' ] } },
  { col: users, op: 'find', q: { id: 'user1' } },
  { col: maps, op: 'update', q: { id: uid() }, u: { } },
  { col: maps, op: 'update', q: { id: map1, createdBy: 'user1' }, u: { labels: [ ] } },
  { col: maps, op: 'update', q: { id: map1, createdBy: 'user1' }, u: { labels: [ 'MA' ] } },
  { col: maps, op: 'update', q: { id: map2, createdBy: 'user1' }, u: { labels: [ 'MA' ] } },
  { col: maps, op: 'query', q: { createdBy: 'user1' } },
  { col: maps, op: 'unset', q: { id: map2, createdBy: 'user1' }, u: { labels: [ 'MA' ] } },
  { col: maps, op: 'update', q: { id: map2, createdBy: 'user1' }, u: { labels: [ 'MA' ] } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { labels: [ 'MA' ] } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { published: 'true' } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { published: 'true' } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { xprops: { state: 'MA' } } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { xprops: { state: 'MA', datasource: '2010' } } },
  { col: maps, op: 'query', q: { published: 'true' } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { accessMap: { 'uid1': { userIDs: [], perm: 3 }} } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: { revisions: [ { id: 'rev1', modifyTime: 'tt' } ] } },
  { col: maps, op: 'update', q: { id: uid(), createdBy: 'user1' }, u: {"accessMap":{"f2eefa86-f579-45e3-9342-6e2fcab87ff8":{"userIDs":[],"perm":3},"94386379-5971-44c6-b96e-c766ca888723":{"userIDs":[],"perm":1}},"clientCount":0,"createTime":"2020-03-18T01:34:14.059Z","createdBy":"2caca709-712f-489a-ac42-ea519c542b35","deleted":false,"description":"","labels":["New States"],"lastActive":"2020-03-18T01:34:14.059Z","loadFailed":false,"maxClients":0,"modifyTime":"2020-03-18T04:51:36.727Z","name":"Illinois","official":false,"requestCount":0,"revisions":[{"id":"f439405b-f3b3-4d01-94fd-614f1fd6a55c","modifyTime":"2020-03-18T01:34:14.059Z","label":""}],"type":"redistrict","xprops":{"state":"IL","datasource":"2010_VD","nDistricts":"3","districtMilestoneFlags":{"pctAssigned10Key":true,"pctAssigned20Key":true,"pctAssigned30Key":true,"pctAssigned40Key":true,"pctAssigned50Key":true,"pctAssigned60Key":true,"pctAssigned70Key":true,"pctAssigned80Key":true,"pctAssigned90Key":true},"imageTime":"2020-03-18T04:51:36.727Z","imageVersion":"11","imageName":"4775be8d-cab2-46db-97cd-31c192be1e00.png"},"id":"760790ff-13bd-45d8-83a4-42ec2c63cbf9"} },
  { col: maps, op: 'query', q: { createdBy: 'user1' } },
];

let prev: FsmLoudOp = null;
for (let i: number = 0; i < ops.length; i++)
{
  let next = new FsmLoudOp(ops[i]);
  next.waitOn(prev);
  tester.waitOn(next);
  prev = next;
}

Object.keys(DT.Schemas).forEach((name: string) => {
    let cleanup = new FsmCleanup(env, `test-${name}`, DT.Schemas[name].Collection);
    cleanup.waitOn(prev);
    tester.waitOn(cleanup);
  });
