import Nano from "nano";
import { AuditRecord, AuditEngine, DatabaseSettings, PNRESETTYPES } from '@digigov-oss/gsis-audit-record-db';

/**
 * @description AuditEngine implementation
 * @note This class is used to implement the methods that must be implemented by the AuditEngine
 * @class CouchDBEngine
 * @implements AuditEngine
 * @param {string} connectionString - connect to db via connection string `http://user:pass@$dbHost:dbPort`
 * @param {DatabaseSettings} dbSettings - database settings (please note the tableName is actualy a DB name for couchdb)
 */
export class CouchDBEngine implements AuditEngine {
    #dbname: string;
    #connection:string;
    #client: any;
    #pnreset:PNRESETTYPES;

    private async couch(): Promise<any> {
      const nano = Nano(this.#connection);
      const dbList = await nano.db.list(); // list all databases
      try {
        if (!dbList.includes(this.#dbname)) {
          // create a new DB if database doesn't exist.
          await nano.db.create(this.#dbname);
          const db = nano.use(this.#dbname);
          console.log("database created successfully");
          return db;
        } else {
          const db = nano.use(this.#dbname);
          return db;
        }
      } catch (err: any) {
        throw new Error(err);
      }
    };

  /** 
   * @description constructor
   * @param {string} connectionString - connect to couchdb via connection string 
   * @param {DatabaseSettings} settings - settings for the database 
   * @memberof CouchDBEngine
   */
     constructor(connectionString:string = "", dbSettings: DatabaseSettings={},pnreset:PNRESETTYPES="innumerable") {
        this.#connection = connectionString!=""?connectionString:"http://admin:admin@localhost:5984";
        this.#dbname = dbSettings.tableName || "audit_records"; //this is actualy a DB name
        const init = async () => {
          return await this.couch();
        }
        this.#client = init();
        this.#pnreset = pnreset;
      }

  /**
   * @description Store a record in the database
   * @param {AuditRecord} record - record to be stored
   * @returns {AuditRecord} - the record stored
   * @memberof FileEngine
   * @method put
   */
   put(record: AuditRecord): AuditRecord {
    return this.#client.then(async (db:any) => {
    try {
       const doc = {
        _id: record.auditTransactionId,
        ...record
       };
       const _rdoc = await db.insert(doc);
      return record;
    } catch (error) {
      throw error;
      }
    });
  }

  /**
   * @description Get a record from the database
   * @param auditTransactionId: string - transaction id
   * @returns {AuditRecord}
   * @memberof FileEngine
   * @method get
   */
   get(auditTransactionId: string): AuditRecord {
    return this.#client.then(async (db:any) => {
      try {
          const rdoc = await db.get(auditTransactionId);
          return rdoc;
          } catch (error) {
               throw error;
            }
    });
   }

  /**
   * @description Generate a new sequence number
   * @param path
   * @returns number
   * @memberof CouchDBEngine
   * @method seq
   */
   seq():number {
   // from update_seq get the numeric part and add 1
    return this.#client.then(async (db:any) => {
      try {
          const rdoc = await db.info();
          //get integer part from 1-xxxx
          const seq = parseInt(rdoc.update_seq.split("-")[0]);
          return seq+1;
          } catch (error) {
               throw error;
            }
    });
  }

  /**
   * @description Generate a new protocol number
   * @param path
   * @returns string
   * @memberof CouchDBEngine
   * @method protocol
   */
   pn():string {
    
   return this.#client.then(async (db:any) => {
    
    const pnreset = this.#pnreset;
    try {
      let protocol_split = "aion";
      let protocol_date =  new Date().toISOString().split('T')[0];
      switch(pnreset){
          case "daily":
           protocol_split = protocol_date;
          break;
          case "monthly":
              protocol_split = protocol_date.split('-')[0]+"-"+protocol_date.split('-')[1];
          break;
          case "yearly":
              protocol_split = protocol_date.split('-')[0];
          break;
          case "innumerable":
              protocol_split = "aion";
          break;
      }
      
      //Create indexes based on pnreset daily, monthly, yearly, innumerable
      const seqName = "prot_"+protocol_split.replace(/-/g, '');
      //create document in couchdb
      let doc = {
        _id: seqName,
        rev: "1"
      };
      //get the document
      try {
        doc = await db.get(seqName);  
      } catch (error) {
        //document doesn't exist
      }
    doc = await db.insert(doc);
    const protocol = parseInt((doc as any)['rev'].split("-")[0]); 
    return protocol+"/"+protocol_date;
    } catch (error) {
      throw error;
    }

   });
  }
}

export default CouchDBEngine;