import { createEffect, createRoot, createSignal } from 'solid-js';
import PouchDB from 'pouchdb';
import indexeddb from 'pouchdb-adapter-indexeddb';

import { sleep, until } from '../lib/async-wait';
import { isEqual } from 'lodash';

PouchDB.plugin(indexeddb);

declare global {
  var localDb: any;
  var db: any;
  var remoteDb: any;
  var currentAccount: any;
  var sync: any;
}

const [state, setState] = createSignal<any>({});
export { state };

export const watchDbLegacy = async () => {
  createRoot(async (dispose) => {
    console.log({ caller: 'watchDbLegacy / start' });

    const { db, localDb, remoteDb, sync, currentAccount } = globalThis;

    const [status, setStatus] = createSignal<any>({});
    const [syncState, setSyncState] = createSignal<any>({});
    const [remoteUrl, setRemoteUrl] = createSignal<string>();

    const updateStatus = async () => {
      const dbInfo = await db.info();
      const localDbInfo = await localDb.info();
      const remoteDbInfo = remoteDb ? await remoteDb.info() : undefined;
      const tasks = PouchDB.activeTasks.list();
      const newStatus = {
        ...status(),
        tasks,
        dbInfo,
        localDbInfo,
        remoteDbInfo,
        //    date: new Date(),
      };
      if (!isEqual(status(), newStatus)) {
        setStatus(newStatus);
      }
    };

    createEffect(() => {
      console.log({
        caller: 'watchDbLegacy',
        state: state(),
        status: status(),
        syncState: syncState(),
        sync,
      });
    });

    createEffect(() => {
      const newState = {
        ...state(),
        dbName: status()?.dbInfo?.db_name,
        localUpdateSeq: status()?.dbInfo?.update_seq,
        remoteUrl: status()?.remoteDbInfo?.host,
        sync: syncState(),
      };
      if (!isEqual(newState, state())) {
        setState(newState);
      }
    });

    updateStatus();

    if (sync) {
      sync
        .on('change', function (info) {
          // handle change
          //          console.log({ caller: 'Sync / change', info });
          if (info.direction === 'push') {
            setSyncState({ ...syncState(), lastSeq: info.change.last_seq });
          }
        })
        .on('paused', function (err) {
          // replication paused (e.g. replication up to date, user went offline)
          setSyncState({ ...syncState(), paused: true });
          //          console.log({ caller: 'Sync / paused', err });
        })
        .on('active', function () {
          // replicate resumed (e.g. new changes replicating, user went back online)
          setSyncState({ ...syncState(), paused: false });
          //          console.log({ caller: 'Sync / active' });
        })
        .on('denied', function (err) {
          // a document failed to replicate (e.g. due to permissions)
          console.error({ caller: 'Sync / denied', err });
        })
        .on('complete', function (info) {
          // handle complete
          console.log({ caller: 'Sync / complete', info });
        })
        .on('error', function (err) {
          // handle error
          console.error({ caller: 'Sync / error', err });
        });
    }

    const timerId = setInterval(updateStatus, 15000);

    const findDocumentsWithoutOrigin = async () => {
      const allDocs = await db.allDocs({ include_docs: true });
      return allDocs.rows
        .filter((row: any) => !row.doc.origin)
        .map((row: any) => row.doc._id);
    };

    createEffect(async () => {
      if (!!state().remoteUrl && remoteUrl() !== state().remoteUrl) {
        setRemoteUrl(state().remoteUrl);
        // Check documents and update without origin
        const documentsWithoutOrigin = await findDocumentsWithoutOrigin();
        console.log({
          caller: 'watchDbLegacy / addOrigin',
          remoteUrl: remoteUrl(),
          nbDocumentsWithoutOrigin: documentsWithoutOrigin.length,
        });
        for (const docId of documentsWithoutOrigin) {
          while (true) {
            await sleep(100);
            const doc = await db.get(docId);
            console.log({
              caller: 'watchDbLegacy / addOrigin',
              remoteUrl: remoteUrl(),
              docId,
              doc,
            });
            try {
              db.put({ ...doc, origin: remoteUrl() });
              break;
            } catch (error) {
              if (error.name === 'conflict') {
                console.log({
                  caller: 'watchDbLegacy / addOrigin',
                  docId,
                  doc,
                  error,
                });
              } else {
                console.error({
                  caller: 'watchDbLegacy / addOrigin',
                  docId,
                  doc,
                  error,
                });
                break;
              }
            }
          }
        }
      }
    });

    // db.compact();
    const openIDb = (name: string) =>
      new Promise((resolve, reject) => {
        const iDb = indexedDB.open(name);
        iDb.onerror = (event: any) => {
          console.error({
            caller: 'watchDbLegacy',
            message: 'open db error',
            target: event.target,
          });
          reject(event.target.errorCode);
        };

        iDb.onsuccess = (event: any) => {
          resolve(event.target.result);
        };
      });

    const getAll = (store: any) =>
      new Promise((resolve, reject) => {
        const request = store.getAll();
        request.onerror = (event: any) => {
          console.error({
            caller: 'watchDbLegacy',
            message: 'getAll error',
            target: event.target,
          });
          reject(event.target.errorCode);
        };

        request.onsuccess = (event: any) => {
          resolve(event.target.result as any[]);
        };
      });

    const getDeletedDocs = async () => {
      const iDb = (await openIDb(`_pouch_${state().dbName}`)) as IDBDatabase;
      const bySequence = iDb.transaction('docs', 'readonly');
      const store = bySequence.objectStore('docs');
      const allDocs = (await getAll(store)) as any[];
      iDb.close();
      const docs = allDocs
        .filter(
          (doc) =>
            !doc.id.startsWith('_local') &&
            doc.deleted === 1 &&
            doc.seq <= state()?.sync?.lastSeq
        )
        .map((doc) => ({ id: doc.id, rev: doc.rev }));
      //   console.log({
      //     caller: 'watchDbLegacy / getDeletedDocs',
      //     // bySequence,
      //     // store,
      //     // allDocs,
      //     docs,
      //     state: state(),gi
      //   });
      return docs;
    };

    while (true) {
      await until(
        () =>
          state()?.sync?.paused &&
          state()?.sync?.lastSeq >= state()?.localUpdateSeq - 1, // I wonder why this can happen !
        1000
      );

      console.log({ caller: 'watchDbLegacy / ready to purge after sync ' });

      try {
        const localPurges = await db.get('_local/purges');
        localPurges._deleted = true;
        await db.put(localPurges);
      } catch (error) {
        console.warn({ caller: 'watchDbLegacy / _local/purges', error });
      }

      const deletedDocs = await getDeletedDocs();

      let i = 0;
      if (deletedDocs.length > 0) {
        console.log({
          caller: 'watchDbLegacy / purge',
          // bySequence,
          // store,
          // allDocs,
          deletedDocs,
          state: state(),
        });
        for (const doc of deletedDocs) {
          await sleep(10);
          try {
            await db.purge(doc.id, doc.rev);
          } catch (err) {
            console.log({
              caller: 'watchDbLegacy / purge',
              doc,
              err,
            });
          }
          i = i + 1;
          if (i % 10 === 0) {
            console.log({
              caller: 'watchDbLegacy / purge',
              nbDeletedDocs: i,
              nbDocsToDelete: deletedDocs.length,
              doc,
            });
          }
          if (i === 1000) {
            break;
          }
        }
      }
      if (i !== deletedDocs.length) {
        await sleep(60000);
      }
    }
  });
};