import { saveAs } from 'file-saver';
import { v4 as uuid } from 'uuid';
import i18n from '@/plugins/i18n';
import { loggerContent } from '@/plugins/logger';
import { loadScriptUrl } from '@content/service/util';
import {
  bridgeTiktokDownloadVideoCheck,
  bridgeTiktokDownloadVideoInc,
  bridgeOpenPopupUrl,
  bridgeVideoBlobStore,
} from '@/bridge/content/background';
import {
  CHANNELS,
  SCRIPT_PATH,
  ICON_PATH,
  ELEMENT_IDS,
  SELECTORS,
  MEDIA_URL_REGEX,
  MEDIA_URL_PREFER_REGEX,
  VIDEO_ID_REGEX,
  BIG_NUMBER_REGEX,
  DOWNLOAD_BTN_STYLE,
  BROWSE_BTN_STYLE,
  DOWNLOAD_ICON_SVG,
  POST_ICON_SVG,
  BRAND_BADGE_STYLE,
  TOOLTIP_STYLE,
  TOOLTIP_ICON_STYLE,
} from './constant';

class TiktokFeedLoader {
  constructor() {
    this.blobResolvers = new Map();
    this.lastVideoUrl = '';
    this.lastMediaUrl = '';
    this.activeMediaUrl = '';
    this.iconUrl = this.resolveIconUrl();
  }

  /**
   *
   * @returns {string}
   */
  resolveIconUrl() {
    try {
      if (chrome && chrome.runtime && chrome.runtime.getURL) {
        return chrome.runtime.getURL(ICON_PATH);
      }
    } catch (error) {
      return '';
    }

    return '';
  }

  async initialize() {
    this.listenBlobReply();
    this.startMediaObserver();
    this.listenVideoPlaying();
    this.startButtonObserver();

    await loadScriptUrl(SCRIPT_PATH);
  }

  listenBlobReply() {
    window.addEventListener('message', (event) => {
      if (event.source !== window) {
        return;
      }

      const data = event.data;

      if (!data || data.channelReply !== CHANNELS.FETCH_BLOB_REPLY) {
        return;
      }

      const resolver = this.blobResolvers.get(data.messageId);

      if (!resolver) {
        return;
      }

      this.blobResolvers.delete(data.messageId);

      if (data.ok && data.blob) {
        resolver(data.blob);
        return;
      }

      resolver(null);
    });
  }

  startMediaObserver() {
    try {
      const observer = new PerformanceObserver((list) => {
        list.getEntries().forEach((entry) => {
          this.captureMediaUrl(entry.name);
        });
      });

      observer.observe({ type: 'resource', buffered: true });
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_MEDIA_OBSERVER_ERROR',
        error,
      });
    }
  }

  listenVideoPlaying() {
    document.addEventListener('playing', () => {
      const mediaUrl = this.getLatestMediaUrl();

      if (mediaUrl) {
        this.activeMediaUrl = mediaUrl;
      }
    }, true);
  }

  /**
   *
   * @param {string} url
   */
  captureMediaUrl(url) {
    if (MEDIA_URL_PREFER_REGEX.test(url)) {
      this.lastVideoUrl = url;
      return;
    }

    if (MEDIA_URL_REGEX.test(url)) {
      this.lastMediaUrl = url;
    }
  }

  /**
   *
   * @returns {string}
   */
  getLatestMediaUrl() {
    if (this.lastVideoUrl) {
      return this.lastVideoUrl;
    }

    try {
      const names = performance.getEntriesByType('resource')
        .map((entry) => entry.name);

      const preferred = names.filter((name) => MEDIA_URL_PREFER_REGEX.test(name));

      if (preferred.length) {
        return preferred[preferred.length - 1];
      }
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_GET_MEDIA_URL_ERROR',
        error,
      });
    }

    if (this.lastMediaUrl) {
      return this.lastMediaUrl;
    }

    try {
      const names = performance.getEntriesByType('resource')
        .map((entry) => entry.name);

      const matched = names.filter((name) => MEDIA_URL_REGEX.test(name));

      if (matched.length) {
        return matched[matched.length - 1];
      }
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_GET_MEDIA_URL_ERROR',
        error,
      });
    }

    return '';
  }

  startButtonObserver() {
    const observer = new MutationObserver(() => {
      this.injectButtons();
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
    });

    setInterval(() => {
      this.injectButtons();
    }, 1000);

    this.injectButtons();
  }

  injectButtons() {
    try {
      if (this.isPhotoPost()) {
        this.removeInjectedButtons();
        return;
      }

      const actionBars = document.querySelectorAll(SELECTORS.ACTION_BAR);

      actionBars.forEach((actionBar) => {
        this.injectButtonForActionBar(actionBar);
      });

      this.injectBrowseButtons();
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_INJECT_BUTTONS_ERROR',
        error,
      });
    }
  }

  removeInjectedButtons() {
    const selector = `.${ELEMENT_IDS.DOWNLOAD_BTN_CLASS}, .${ELEMENT_IDS.POST_BTN_CLASS}`;

    document.querySelectorAll(selector).forEach((element) => {
      element.remove();
    });
  }

  injectBrowseButtons() {
    if (this.isPhotoPost()) {
      return;
    }

    const likeIcon = document.querySelector(SELECTORS.BROWSE_LIKE_ICON);

    if (!likeIcon) {
      return;
    }

    const likeButton = likeIcon.closest('button');

    if (!likeButton) {
      return;
    }

    const row = likeButton.parentElement;

    if (!row) {
      return;
    }

    if (row.querySelector(`.${ELEMENT_IDS.DOWNLOAD_BTN_CLASS}`)) {
      return;
    }

    const postButton = this.createPostButton(true);
    const downloadButton = this.createDownloadButton(true);

    row.insertBefore(postButton, row.firstChild);
    row.insertBefore(downloadButton, row.firstChild);
  }

  /**
   *
   * @returns {boolean}
   */
  isPhotoPost() {
    if (window.location.pathname.indexOf('/photo/') !== -1) {
      return true;
    }

    return false;
  }

  /**
   *
   * @param {Element} actionBar
   */
  injectButtonForActionBar(actionBar) {
    const article = actionBar.closest(SELECTORS.ARTICLE_CONTAINER);

    if (article && !article.querySelector('video')) {
      return;
    }

    if (!actionBar.querySelector(`.${ELEMENT_IDS.POST_BTN_CLASS}`)) {
      const postButton = this.createPostButton();
      actionBar.insertBefore(postButton, actionBar.firstChild);
    }

    if (actionBar.querySelector(`.${ELEMENT_IDS.DOWNLOAD_BTN_CLASS}`)) {
      return;
    }

    const button = this.createDownloadButton();

    actionBar.insertBefore(button, actionBar.firstChild);
  }

  /**
   *
   * @param {HTMLButtonElement} button
   * @param {string} color
   */
  setButtonBackground(button, color) {
    button.style.setProperty('background', color, 'important');
  }

  /**
   *
   * @param {boolean} [compact]
   * @returns {HTMLButtonElement}
   */
  createDownloadButton(compact) {
    const button = document.createElement('button');

    let baseStyle = DOWNLOAD_BTN_STYLE;

    if (compact) {
      baseStyle = BROWSE_BTN_STYLE;
    }

    button.type = 'button';
    button.className = ELEMENT_IDS.DOWNLOAD_BTN_CLASS;
    button.style.cssText = `${baseStyle};position: relative;`;
    button.innerHTML = DOWNLOAD_ICON_SVG;

    if (this.iconUrl) {
      const badge = document.createElement('img');
      badge.src = this.iconUrl;
      badge.alt = 'AutoShopee';
      badge.style.cssText = BRAND_BADGE_STYLE;
      button.appendChild(badge);
    }

    button.addEventListener('mouseover', () => {
      this.setButtonBackground(button, '#d8431f');
      this.showTooltip(button, i18n.t('TIKTOK_DOWNLOAD_VIDEO_BTN'));
    });

    button.addEventListener('mouseout', () => {
      this.setButtonBackground(button, '#ee4d2d');
      this.hideTooltip();
    });

    button.addEventListener('click', (clickEvent) => {
      clickEvent.preventDefault();
      clickEvent.stopPropagation();
      this.onClickDownload(button).catch(() => {});
    });

    return button;
  }

  /**
   *
   * @param {boolean} [compact]
   * @returns {HTMLButtonElement}
   */
  createPostButton(compact) {
    const button = document.createElement('button');

    let baseStyle = DOWNLOAD_BTN_STYLE;

    if (compact) {
      baseStyle = BROWSE_BTN_STYLE;
    }

    button.type = 'button';
    button.className = ELEMENT_IDS.POST_BTN_CLASS;
    button.style.cssText = `${baseStyle};position: relative;`;
    button.innerHTML = POST_ICON_SVG;

    if (this.iconUrl) {
      const badge = document.createElement('img');
      badge.src = this.iconUrl;
      badge.alt = 'AutoShopee';
      badge.style.cssText = BRAND_BADGE_STYLE;
      button.appendChild(badge);
    }

    button.addEventListener('mouseover', () => {
      this.setButtonBackground(button, '#d8431f');
      this.showTooltip(button, i18n.t('SHOPEE_VIDEO_POST_BTN'));
    });

    button.addEventListener('mouseout', () => {
      this.setButtonBackground(button, '#ee4d2d');
      this.hideTooltip();
    });

    button.addEventListener('click', (clickEvent) => {
      clickEvent.preventDefault();
      clickEvent.stopPropagation();
      this.onClickPostShopee(button).catch(() => {});
    });

    return button;
  }

  /**
   *
   * @param {HTMLButtonElement} button
   * @returns {Promise<void>}
   */
  async onClickPostShopee(button) {
    const candidates = this.getMediaCandidates();

    if (!candidates.length) {
      this.showToast(i18n.t('SHOPEE_VIDEO_POST_NO_DATA'));
      return;
    }

    const article = this.resolveArticle(button);
    const videoInfo = this.resolveVideoInfo(article);

    let title = '';

    if (videoInfo.author) {
      title = `@${videoInfo.author}`;
    }

    this.setButtonLoading(button, true);
    this.showToast(i18n.t('SHOPEE_VIDEO_POST_PREPARING'));

    try {
      const result = await this.fetchVideoBlob();

      if (!result) {
        throw new Error('TIKTOK_POST_BLOB_EMPTY');
      }

      const base64 = await this.blobToBase64(result.blob);
      const { blobId } = await bridgeVideoBlobStore({
        base64,
        contentType: result.blob.type || 'video/mp4',
      });

      await bridgeOpenPopupUrl({
        path: '/shopee/video/compose',
        qs: {
          source: 'tiktok',
          blobId,
          sourceUrl: result.mediaUrl,
          title,
        },
      });
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_POST_SHOPEE_ERROR',
        error,
      });
      this.showToast(i18n.t('SHOPEE_VIDEO_POST_NO_DATA'));
    } finally {
      this.setButtonLoading(button, false);
    }
  }

  /**
   *
   * @param {Blob} blob
   * @returns {Promise<string>}
   */
  blobToBase64(blob) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      reader.onload = () => {
        const result = String(reader.result || '');
        const commaIndex = result.indexOf(',');

        if (commaIndex >= 0) {
          resolve(result.slice(commaIndex + 1));
          return;
        }

        resolve(result);
      };

      reader.onerror = () => {
        reject(reader.error);
      };

      reader.readAsDataURL(blob);
    });
  }

  /**
   *
   * @returns {HTMLElement}
   */
  ensureTooltip() {
    let tooltip = document.getElementById(ELEMENT_IDS.TOOLTIP_ID);

    if (tooltip) {
      return tooltip;
    }

    tooltip = document.createElement('div');
    tooltip.id = ELEMENT_IDS.TOOLTIP_ID;
    tooltip.style.cssText = TOOLTIP_STYLE;

    let iconHtml = '';

    if (this.iconUrl) {
      iconHtml = `<img src="${this.iconUrl}" alt="AutoShopee" style="${TOOLTIP_ICON_STYLE}" />`;
    }

    tooltip.innerHTML = `
      ${iconHtml}
      <span style="display:flex;flex-direction:column;line-height:1.35;">
        <span id="${ELEMENT_IDS.TOOLTIP_TITLE_ID}"></span>
        <span style="font-weight:400;opacity:0.75;font-size:11px;">${i18n.t('CREATED_BY_COPYRIGHT')}</span>
      </span>
    `;

    document.body.appendChild(tooltip);

    return tooltip;
  }

  /**
   *
   * @param {HTMLElement} button
   * @param {string} title
   */
  showTooltip(button, title) {
    const tooltip = this.ensureTooltip();
    const titleEl = tooltip.querySelector(`#${ELEMENT_IDS.TOOLTIP_TITLE_ID}`);

    if (titleEl) {
      titleEl.textContent = title;
    }

    const rect = button.getBoundingClientRect();

    tooltip.style.display = 'flex';

    const tooltipRect = tooltip.getBoundingClientRect();
    let left = rect.left - tooltipRect.width - 12;

    if (left < 8) {
      left = rect.right + 12;
    }

    let top = rect.top + (rect.height / 2) - (tooltipRect.height / 2);

    if (top < 8) {
      top = 8;
    }

    tooltip.style.left = `${left}px`;
    tooltip.style.top = `${top}px`;
  }

  hideTooltip() {
    const tooltip = document.getElementById(ELEMENT_IDS.TOOLTIP_ID);

    if (tooltip) {
      tooltip.style.display = 'none';
    }
  }

  /**
   *
   * @returns {HTMLVideoElement|null}
   */
  getPlayingVideo() {
    const videos = Array.from(document.querySelectorAll('video'));

    const playing = videos.find((video) => {
      return !video.paused && video.currentTime > 0;
    });

    if (playing) {
      return playing;
    }

    return null;
  }

  /**
   *
   * @param {HTMLButtonElement} button
   * @returns {Element|null}
   */
  resolveArticle(button) {
    const playing = this.getPlayingVideo();

    if (playing) {
      const articleByVideo = playing.closest(SELECTORS.ARTICLE_CONTAINER);

      if (articleByVideo) {
        return articleByVideo;
      }
    }

    return button.closest(SELECTORS.ARTICLE_CONTAINER);
  }

  /**
   *
   * @param {Element} article
   * @returns {{awemeId: string, author: string}}
   */
  resolveVideoInfo(article) {
    const info = { awemeId: '', author: '' };

    if (!article) {
      info.author = this.resolveBrowseAuthor();
      info.awemeId = this.resolveAwemeIdFromUrl();
      return info;
    }

    const authorLink = article.querySelector(SELECTORS.AUTHOR_LINK);

    if (authorLink) {
      info.author = (authorLink.getAttribute('href') || '').replace('/@', '').trim();
    }

    info.awemeId = this.resolveAwemeIdFromArticle(article);

    return info;
  }

  /**
   *
   * @param {Element} article
   * @returns {string}
   */
  resolveAwemeIdFromArticle(article) {
    const excludeHref = Array.from(article.querySelectorAll('a[href*="/music/"], a[href*="/tag/"], a[href*="/place/"]'))
      .map((link) => link.getAttribute('href') || '')
      .join(' ');

    const numbers = article.outerHTML.match(BIG_NUMBER_REGEX) || [];

    const candidate = numbers.find((number) => {
      return !excludeHref.includes(number);
    });

    if (candidate) {
      return candidate;
    }

    return this.resolveAwemeIdFromUrl();
  }

  /**
   *
   * @returns {string}
   */
  resolveBrowseAuthor() {
    const usernameEl = document.querySelector(SELECTORS.BROWSE_USERNAME);

    if (usernameEl && usernameEl.textContent) {
      return usernameEl.textContent.trim();
    }

    return '';
  }

  /**
   *
   * @returns {string}
   */
  resolveAwemeIdFromUrl() {
    const matched = window.location.href.match(VIDEO_ID_REGEX);

    if (matched && matched[1]) {
      return matched[1];
    }

    return '';
  }

  /**
   *
   * @param {HTMLButtonElement} button
   * @returns {Promise<void>}
   */
  async onClickDownload(button) {
    const quota = await this.checkQuota();

    if (!quota.allow) {
      if (quota.reason === 'login') {
        this.showToast(i18n.t('TIKTOK_DOWNLOAD_VIDEO_LOGIN'));

        const confirmLogin = window.confirm(i18n.t('TIKTOK_DOWNLOAD_VIDEO_LOGIN_CONFIRM'));

        if (confirmLogin) {
          bridgeOpenPopupUrl({ path: '/login' }).catch(() => {});
        }

        return;
      }

      this.showToast(i18n.t('TIKTOK_DOWNLOAD_VIDEO_LIMIT', {
        current: quota.used,
        limit: quota.dailyLimit,
      }));
      return;
    }

    const candidates = this.getMediaCandidates();

    if (!candidates.length) {
      this.showToast(i18n.t('TIKTOK_DOWNLOAD_VIDEO_NO_DATA'));
      return;
    }

    const article = this.resolveArticle(button);
    const videoInfo = this.resolveVideoInfo(article);

    this.setButtonLoading(button, true);
    this.showToast(i18n.t('TIKTOK_DOWNLOAD_VIDEO_START'));

    try {
      const result = await this.fetchVideoBlob();

      if (!result) {
        throw new Error('TIKTOK_BLOB_EMPTY');
      }

      const fileName = this.buildFileName(videoInfo.author, videoInfo.awemeId);
      saveAs(result.blob, fileName);

      bridgeTiktokDownloadVideoInc({ awemeId: videoInfo.awemeId, author: videoInfo.author }).catch(() => {});
      this.showToast(i18n.t('TIKTOK_DOWNLOAD_VIDEO_SUCCESS'));
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_DOWNLOAD_VIDEO_ERROR',
        error,
      });
      this.showToast(i18n.t('TIKTOK_DOWNLOAD_VIDEO_ERROR'));
    } finally {
      this.setButtonLoading(button, false);
    }
  }

  /**
   *
   * @returns {Promise<{allow: boolean, used: number, dailyLimit: number}>}
   */
  async checkQuota() {
    try {
      const result = await bridgeTiktokDownloadVideoCheck();

      if (result && typeof result.allow === 'boolean') {
        return result;
      }
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_CHECK_QUOTA_ERROR',
        error,
      });
    }

    return { allow: true, used: 0, dailyLimit: 0 };
  }

  /**
   * Danh sách URL ứng viên, ưu tiên video chắc chắn (mime_type=video_mp4)
   * mới nhất trước. TikTok có thể trả phụ đề WebVTT trên URL/header giả dạng
   * video/mp4 nên URL nào cũng phải sniff nội dung blob trước khi dùng.
   * @returns {string[]}
   */
  getMediaCandidates() {
    const candidates = [];

    const pushCandidate = (url) => {
      if (url && candidates.indexOf(url) === -1) {
        candidates.push(url);
      }
    };

    if (MEDIA_URL_PREFER_REGEX.test(this.activeMediaUrl)) {
      pushCandidate(this.activeMediaUrl);
    }

    pushCandidate(this.lastVideoUrl);

    try {
      const names = performance.getEntriesByType('resource')
        .map((entry) => entry.name);

      names
        .filter((name) => MEDIA_URL_PREFER_REGEX.test(name))
        .reverse()
        .forEach(pushCandidate);

      names
        .filter((name) => MEDIA_URL_REGEX.test(name) && !MEDIA_URL_PREFER_REGEX.test(name))
        .reverse()
        .forEach(pushCandidate);
    } catch (error) {
      loggerContent.error({
        msg: 'TIKTOK_GET_MEDIA_CANDIDATES_ERROR',
        error,
      });
    }

    pushCandidate(this.lastMediaUrl);
    pushCandidate(this.activeMediaUrl);

    return candidates.slice(0, 5);
  }

  /**
   * Fetch lần lượt các URL ứng viên, trả về blob video thật đầu tiên.
   * @returns {Promise<{blob: Blob, mediaUrl: string}|null>}
   */
  async fetchVideoBlob() {
    const candidates = this.getMediaCandidates();

    for (let i = 0; i < candidates.length; i += 1) {
      const mediaUrl = candidates[i];
      // eslint-disable-next-line no-await-in-loop
      const blob = await this.requestBlob(mediaUrl);

      if (blob) {
        // eslint-disable-next-line no-await-in-loop
        const isVideo = await this.isBlobVideo(blob);

        if (isVideo) {
          return { blob, mediaUrl };
        }
      }
    }

    return null;
  }

  /**
   *
   * @param {Blob} blob
   * @returns {Promise<boolean>}
   */
  async isBlobVideo(blob) {
    const type = (blob.type || '').toLowerCase();

    if (type.indexOf('text') !== -1) {
      return false;
    }

    if (type.indexOf('vtt') !== -1) {
      return false;
    }

    if (type.indexOf('json') !== -1) {
      return false;
    }

    if (type.indexOf('html') !== -1) {
      return false;
    }

    try {
      const head = await blob.slice(0, 16).text();

      if (head.indexOf('WEBVTT') === 0) {
        return false;
      }

      if (head.indexOf('<!DOCTYPE') === 0) {
        return false;
      }

      if (head.indexOf('{') === 0) {
        return false;
      }
    } catch (error) {
      return true;
    }

    return true;
  }

  /**
   *
   * @param {string} url
   * @returns {Promise<Blob|null>}
   */
  requestBlob(url) {
    const messageId = uuid();

    return new Promise((resolve) => {
      this.blobResolvers.set(messageId, resolve);

      window.postMessage({
        channelRequest: CHANNELS.FETCH_BLOB_REQUEST,
        messageId,
        payload: { url },
      }, '*');

      setTimeout(() => {
        if (this.blobResolvers.has(messageId)) {
          this.blobResolvers.delete(messageId);
          resolve(null);
        }
      }, 60000);
    });
  }

  /**
   *
   * @param {string} author
   * @param {string} awemeId
   * @returns {string}
   */
  buildFileName(author, awemeId) {
    const safeAuthor = (author || 'tiktok').replace(/[^a-zA-Z0-9_.-]/g, '_');

    if (awemeId) {
      return `tiktok_${safeAuthor}_${awemeId}.mp4`;
    }

    return `tiktok_${safeAuthor}_${Date.now()}.mp4`;
  }

  /**
   *
   * @param {HTMLButtonElement} button
   * @param {boolean} isLoading
   */
  setButtonLoading(button, isLoading) {
    if (isLoading) {
      button.style.opacity = '0.5';
      button.style.pointerEvents = 'none';
      return;
    }

    button.style.opacity = '1';
    button.style.pointerEvents = 'auto';
  }

  /**
   *
   * @param {string} message
   */
  showToast(message) {
    let toast = document.getElementById(ELEMENT_IDS.TOAST_ID);

    if (!toast) {
      toast = document.createElement('div');
      toast.id = ELEMENT_IDS.TOAST_ID;
      toast.style.cssText = [
        'position: fixed',
        'bottom: 32px',
        'left: 50%',
        'transform: translateX(-50%)',
        'z-index: 999999',
        'padding: 10px 18px',
        'border-radius: 8px',
        'background: rgba(22, 24, 35, 0.92)',
        'color: #ffffff',
        'font-size: 14px',
        'font-weight: 500',
        'box-shadow: 0 4px 14px rgba(0,0,0,0.25)',
        'pointer-events: none',
      ].join(';');
      document.body.appendChild(toast);
    }

    toast.textContent = message;
    toast.style.display = 'block';

    if (this.toastTimer) {
      clearTimeout(this.toastTimer);
    }

    this.toastTimer = setTimeout(() => {
      toast.style.display = 'none';
    }, 2500);
  }
}

const tiktokFeedLoader = new TiktokFeedLoader();

tiktokFeedLoader.initialize().catch((error) => {
  loggerContent.error({
    msg: 'TIKTOK_LOADER_INIT_ERROR',
    error,
  });
});
