import delay from 'delay';
import { browser } from 'wxt/browser';
import { workerConnect, workerEvent } from '@/socket/worker';
import { handlderJobFetch, getFetchJobId, handlerJobFetchWorkerId } from '@/api/worker/fetch';
import { getTabIdReadyFetch } from '@/bridge/shopee/api';
import { checkCrawlerDetected } from '@/api/shopee/getter';
import { getShopeeSellerFallbackInfo } from '@/api/shopee/seller';
import { bridgeTabFetchShopeeUrl } from '@/bridge/background/content';
import { reloadTabId } from '@/browser/tab';
import { isDevMode } from '@/config/env';
import { loggerWorker } from '@/plugins/logger';
import { configApp } from '@/config/app';
import {
  storageUserName, storageUserId,
  storageBrowserIp, storageBrowserName, storageProfileId, storageAppUuid,
} from '@/plugins/storage/items';

const reloadSetUpWorker = {
  noTabId: 10000,
  crawlerDetected: 60000,
};

class WorkerFetchUrlService {
  constructor() {
    this.workerData = {
      isConnected: true,
      isActive: true,
      isReady: false,
      lastestLog: '',
      name: '',
      id: '',
      fetchJob: {
        count: 0,
        id: '',
        lastest: null,
      },
    };
    this.socketWorker = null;
    this.socketId = null;
    this.isGlobalReload = false;
  }

  logMessage(msg, data) {
    loggerWorker.log({ msg, ...data });
  }

  logError(msg, data) {
    loggerWorker.error({ msg, ...data });
  }

  async handleTabReload(tabId, waiting = 100000) {
    this.logMessage('FETCH_RELOAD_TAB_ID', { tabId });
    reloadTabId(tabId);
    return delay(waiting);
  }

  async fetchUrlWorker(payload) {
    const { workerData } = this;

    if (!(workerData.isConnected && workerData.isActive)) {
      return false;
    }

    if (!workerData.isReady) {
      this.logError('WORKER_READY_DOWN', { workerData });
      throw new Error('FETCH_URL_WORKER_DOWN');
    }

    const { jobId, url: fetchUrl, post: postData } = payload;
    const isFetchPost = !!postData;

    this.logMessage('FETCH_URL_START', { workerData, jobId, fetchUrl });

    const tabData = await getTabIdReadyFetch();

    if (!tabData) {
      this.logError('WORKER_NO_TAB_ERROR', {
        tabData,
        workerData,
        jobId,
        fetchUrl,
      });
      this.setWorkerDown('NO_TAB_ID');
      setTimeout(() => this.setWorkerUp(), reloadSetUpWorker.noTabId);
      throw new Error('FETCH_URL_NO_TAB_ERROR');
    }

    const tabId = tabData.id;

    if (this.isGlobalReload) {
      this.isGlobalReload = false;
      await this.handleTabReload(tabId);
    }

    this.logMessage('FETCH_URL_SENDING', { tabId, workerName: workerData.name, fetchUrl });

    if (jobId) {
      await getFetchJobId(jobId, isFetchPost);
    }

    const payloadSend = {
      url: fetchUrl,
      method: isFetchPost ? 'POST' : 'GET',
      body: postData || null,
    };

    const fetchValue = await bridgeTabFetchShopeeUrl(tabId, payloadSend);

    this.logMessage('FETCH_URL_REQUEST', { tabId, workerName: workerData.name, fetchUrl });

    if (checkCrawlerDetected(fetchValue)) {
      this.logMessage('FETCH_URL_ERROR', {
        workerName: workerData.name,
        fetchUrl,
        fetchValue,
        workerData,
      });
      this.setWorkerDown('CRAWLER_DETECTED');
      setTimeout(() => this.setWorkerUp(), reloadSetUpWorker.crawlerDetected);
      throw new Error('FETCH_URL_CRAWLER_DETECTED');
    }

    this.logMessage('FETCH_URL_FINISH', {
      workerName: workerData.name,
      fetchUrl,
      fetchValue,
      workerData,
    });

    workerData.fetchJob.count++;
    workerData.fetchJob.lastest = new Date();

    if (jobId) {
      await handlderJobFetch(jobId, fetchValue, `background:${workerData.name}`, isFetchPost).catch((error) => {
        this.logError('FETCH_SAVE_JOB_ERROR', { error });
      });
    }

    return fetchValue;
  }

  async setWorkerIndentify() {
    const { workerData, socketWorker } = this;

    const [username, userId, browserIp, browserName, profileId, appId] = await Promise.all([
      storageUserName.getValue(),
      storageUserId.getValue(),
      storageBrowserIp.getValue(),
      storageBrowserName.getValue(),
      storageProfileId.getValue(),
      storageAppUuid.getValue(),
    ]);

    const payloadEmit = {
      from: 'service-worker',
      workerName: workerData.name,
      workerId: workerData.id,
      version: configApp.verText,
      manifest: configApp.manifest,
      username,
      userId,
      browserIp,
      browserName,
      profileId,
      appId,
    };

    this.logMessage('SET_WORKER_INDENTIFY', payloadEmit);

    if (socketWorker) {
      socketWorker.emit(workerEvent.workerSetIndentify, payloadEmit);
    }
  }

  setWorkerUp(log = '') {
    const { workerData, socketWorker } = this;
    this.logMessage('SET_WORKER_UP', { workerName: workerData.name });
    workerData.lastestLog = log;
    workerData.isReady = true;
    const payloadWorkerEmit = {
      workerName: workerData.name,
      workerId: workerData.id,
    };

    if (socketWorker) {
      socketWorker.emit(workerEvent.workerReadyUp, payloadWorkerEmit);
    }
  }

  setWorkerDown(log = '') {
    const { workerData, socketWorker } = this;
    this.logMessage('SET_WORKER_DOWN', { workerName: workerData.name });
    workerData.lastestLog = log;
    workerData.isReady = false;
    const payloadWorkerEmit = {
      workerName: workerData.name,
      workerId: workerData.id,
    };

    if (socketWorker) {
      socketWorker.emit(workerEvent.workerReadyDown, payloadWorkerEmit);
    }
  }

  getWorkerState() {
    const { workerData } = this;
    return workerData.isReady && workerData.isActive && workerData.isConnected ? 'WORKER UP' : 'WORKER DOWN';
  }

  async startService(isFocre = false) {
    if (isDevMode && !isFocre) {
      this.logMessage('SOCKET_WORKER_SKIP_DEV_MODE');

      return null;
    }

    browser.runtime.sendMessage('PING').catch((error) => {
      this.isGlobalReload = true;
      this.logError('PING_ERROR', { error: error.message });
    });

    const sellerFallback = await getShopeeSellerFallbackInfo();

    this.workerData.isReady = !!(sellerFallback && sellerFallback.shopId && sellerFallback.username);

    if (!this.workerData.isReady) {
      this.logError('WORKER_NO_READY_LOGIN');
      return false;
    }

    this.logMessage('WORKER_READY_CONNECT');

    this.workerData.name = sellerFallback.username;
    this.workerData.id = sellerFallback.shopId;

    this.socketWorker = workerConnect();
    this.socketId = this.socketWorker.id;

    if (true) {
      globalThis._fetchWorkerApi = {
        fetchUrl: (payload) => this.fetchUrlWorker(payload),
        getSocketId: () => this.socketWorker.id,
        getSocketClient: () => this.socketWorker,
        getWorkerState: () => this.getWorkerState(),
        getWorkerData: () => this.workerData,
        setWorkerUp: () => this.setWorkerUp(),
        setWorkerDown: () => this.setWorkerDown(),
        setWorkerIndentify: () => this.setWorkerIndentify(),
      };
    }

    this.socketWorker.on(workerEvent.fetchUrlJobRecived, (payloadFetch) => {
      this.fetchUrlWorker(payloadFetch)
        .then((fetchDone) => this.logMessage('FETCH_URL_DONE', { fetchDone }))
        .catch((fetchError) => this.logMessage('FETCH_URL_ERROR', { fetchError }));
    });

    this.socketWorker.on(workerEvent.fetchUrlWorkerId, (payloadFetch) => {
      const { jobId } = payloadFetch;
      this.fetchUrlWorker({ ...payloadFetch, jobId: '' })
        .then((jobValue) => handlerJobFetchWorkerId(jobId, jobValue))
        .catch((error) => handlerJobFetchWorkerId(jobId, error));
    });

    this.socketWorker.on(workerEvent.connect, async () => {
      this.socketId = this.socketWorker.id;
      this.workerData.isConnected = true;
      this.workerData.isActive = true;
      this.setWorkerUp();
      this.setWorkerIndentify();
      this.logMessage('SOCKET_WORKER_CONNECT', { id: this.socketId });
    });

    this.socketWorker.on(workerEvent.disconnected, () => {
      this.workerData.isConnected = false;
      this.workerData.isActive = false;
      this.logError('SOCKET_WORKER_DISCONNECT', { id: this.socketId });
    });

    return this.socketWorker;
  }
}

const workerFetchService = async (isFocre = false) => {
  const FetchApi = new WorkerFetchUrlService();

  return FetchApi.startService(isFocre);
};

export default workerFetchService;
