import lodash from 'lodash';
import { loggerShopee } from '@/plugins/logger';
import { apiVideoMms } from './init';
import { getShopeeSellerInfo } from '../get';
import {
  getVideoVidInfo,
  createVideoTask,
  editVideoTask,
  postVideoTask,
  findPublishedVideoId,
  getVideoDetail,
  buildVideoInfoPatch,
} from './creator';
import {
  bufferToWordArray,
  buildS3PutHeaders,
  sha256Hex,
  md5Hex,
  amzDateNow,
  decryptVodAccessKey,
} from './upload.util';
import {
  SHOPEE_VIDEO_BIZ_VOD,
  SHOPEE_VIDEO_MEDIATYPE_VOD,
  SHOPEE_VIDEO_MMS_VERSION,
  SHOPEE_VIDEO_REGION,
  SHOPEE_VIDEO_TASK_STATUS,
} from './video.constant';

/**
 * @returns {Promise<number>}
 */
export const getVideoUserId = async () => {
  const sellerInfo = await getShopeeSellerInfo();

  if (sellerInfo && sellerInfo.shop_id) {
    return Number(sellerInfo.shop_id);
  }

  return 0;
};

/**
 * @param {object} config
 * @param {import('@type/Shopee/Video/Creator').ShopeeVideoRequestOptions} [opts]
 * @returns {Promise<any>}
 */
export const mmsRequest = async (config, opts = {}) => {
  const {
    errorMsg, dataPath, defaultValue, errorContext = {},
  } = opts;

  try {
    const result = await apiVideoMms.request(config);

    if (dataPath !== undefined) {
      return lodash.get(result, dataPath, defaultValue);
    }

    return result;
  } catch (error) {
    if (errorMsg) {
      loggerShopee.error({
        msg: errorMsg,
        error,
        ...errorContext,
      });
    }

    return defaultValue;
  }
};

/**
 * @param {number} [reportTime]
 * @returns {Promise<import('@type/Shopee/Video/Mms').ShopeeVideoPreuploadVod|null>}
 */
export const preuploadVideoVod = async (reportTime = Date.now()) => {
  const userId = await getVideoUserId();

  return mmsRequest(
    {
      method: 'POST',
      url: '/uploadapi/api/v1/vod/preupload',
      data: {
        biz: SHOPEE_VIDEO_BIZ_VOD,
        ver: 2,
        mediatype: SHOPEE_VIDEO_MEDIATYPE_VOD,
        reportdata: {
          sdkversion: SHOPEE_VIDEO_MMS_VERSION,
          appversion: SHOPEE_VIDEO_MMS_VERSION,
          ostype: '2',
          userid: String(userId),
          reporttime: reportTime,
        },
      },
    },
    { dataPath: 'data', errorMsg: 'PREUPLOAD_VIDEO_VOD_ERROR', defaultValue: null },
  );
};

/**
 * @param {object} reportPayload
 * @returns {Promise<import('@type/Shopee/Video/Mms').ShopeeVideoReportUploadResult|null>}
 */
export const reportUploadVideoVod = async (reportPayload) => {
  return mmsRequest(
    { method: 'POST', url: '/uploadapi/api/v1/vod/reportupload', data: reportPayload },
    { dataPath: 'data', errorMsg: 'REPORT_UPLOAD_VIDEO_VOD_ERROR', defaultValue: null },
  );
};

/**
 * @param {import('@type/Shopee/Video/Mms').ShopeeVideoPreuploadService} service
 * @param {string} vid
 * @param {ArrayBuffer} arrayBuffer
 * @returns {Promise<boolean>}
 */
export const uploadVodBytes = async (service, vid, arrayBuffer) => {
  const host = new URL(service.uploaddomain).host;
  const canonicalUri = `/mms/${vid}.mp4`;
  const wordArray = bufferToWordArray(arrayBuffer);
  const bodyHash = sha256Hex(wordArray);

  const headers = buildS3PutHeaders({
    accessKey: decryptVodAccessKey(service.access_key, SHOPEE_VIDEO_BIZ_VOD),
    secretKey: service.secret_key,
    host,
    canonicalUri,
    bodyLength: arrayBuffer.byteLength,
    bodyHash,
    mediaId: vid,
    amzDate: amzDateNow(),
  });

  const putHeaders = { ...headers };
  delete putHeaders.host;
  delete putHeaders['content-length'];

  const response = await fetch(`${service.uploaddomain}/mms/${vid}.mp4?x-id=PutObject`, {
    method: 'PUT',
    headers: putHeaders,
    body: arrayBuffer,
  });

  if (!response.ok) {
    throw new Error(`SHOPEE_VIDEO_S3_PUT_${response.status}`);
  }

  return true;
};

/**
 * @param {import('@type/Shopee/Video/Mms').ShopeeVideoMeta} videoMeta
 * @returns {Promise<import('@type/Shopee/Video/Mms').ShopeeVideoPublishTaskResult|null>}
 */
export const publishVideoTask = async (videoMeta) => {
  const taskId = await createVideoTask();

  if (!taskId) {
    return null;
  }

  const taskItem = {
    taskId,
    mmsVid: videoMeta.mmsVid,
    allowInfo: videoMeta.allowInfo || { allowDuet: true, allowStitch: true },
    coverFrameTime: videoMeta.coverFrameTime || 0,
    cover: videoMeta.cover,
    caption: videoMeta.caption || '',
    productList: videoMeta.productList || [],
    scheduledInfo: videoMeta.scheduledInfo || { scheduledPost: false },
    duration: videoMeta.duration || 0,
    width: videoMeta.width || 0,
    height: videoMeta.height || 0,
  };

  const editResult = await editVideoTask([taskItem]);
  const editStatus = lodash.get(editResult, '0.status', 0);

  if (editStatus !== SHOPEE_VIDEO_TASK_STATUS.READY) {
    return { taskId, posted: false, status: editStatus };
  }

  const postResult = await postVideoTask([taskId]);
  const postStatus = lodash.get(postResult, '0.status', 0);

  return {
    taskId,
    posted: postStatus === SHOPEE_VIDEO_TASK_STATUS.POSTED,
    status: postStatus,
  };
};

/**
 * @param {Blob} fileBlob
 * @param {object} [meta]
 * @returns {Promise<object|null>}
 */
export const publishVideo = async (fileBlob, meta = {}) => {
  const arrayBuffer = await fileBlob.arrayBuffer();
  const wordArray = bufferToWordArray(arrayBuffer);
  const fsize = arrayBuffer.byteLength;
  const md5 = md5Hex(wordArray);

  const pre = await preuploadVideoVod();

  if (!(pre && pre.services && pre.services.length)) {
    loggerShopee.error({ msg: 'PUBLISH_VIDEO_PREUPLOAD_FAILED', pre });
    return { uploaded: false, step: 'preupload' };
  }

  const service = pre.services.find((s) => s.serviceid === 'shopeeuss') || pre.services[0];
  const vid = pre.vid;

  try {
    await uploadVodBytes(service, vid, arrayBuffer);
  } catch (error) {
    loggerShopee.error({ msg: 'PUBLISH_VIDEO_PUT_BYTES_ERROR', error, vid });
    return {
      vid,
      uploaded: false,
      step: 'putBytes',
      statusCode: lodash.get(error, 'response.status', null),
      error: String(lodash.get(error, 'message', error)).slice(0, 200),
    };
  }

  const videourl = String(service.urlformat || '')
    .replace('{vid}', vid)
    .replace('{extend}', 'mp4');

  const userId = await getVideoUserId();

  await reportUploadVideoVod({
    vid,
    mid: '',
    serviceid: service.serviceid,
    updomain: service.uploaddomain,
    biz: SHOPEE_VIDEO_BIZ_VOD,
    region: SHOPEE_VIDEO_REGION,
    extendid: vid,
    fsize,
    md5,
    videourl,
    code: 0,
    reportdata: {
      cost: 1,
      sdkversion: SHOPEE_VIDEO_MMS_VERSION,
      ostype: '2',
      reporttime: Date.now(),
      userid: String(userId),
    },
    fileinfos: { mediatype: SHOPEE_VIDEO_MEDIATYPE_VOD },
  });

  let info = null;

  for (let i = 0; i < 15; i += 1) {
    info = await getVideoVidInfo(vid);

    if (info && info.duration) {
      break;
    }

    await new Promise((resolve) => {
      setTimeout(resolve, 2000);
    });
  }

  const publishResult = await publishVideoTask({
    mmsVid: vid,
    cover: meta.cover || (info && info.cover) || '',
    caption: meta.caption || '',
    productList: meta.productList || [],
    allowInfo: meta.allowInfo || { allowDuet: true, allowStitch: true },
    duration: meta.duration || (info && info.duration) || 0,
    width: meta.width || 0,
    height: meta.height || 0,
    scheduledInfo: meta.scheduledInfo || { scheduledPost: false },
  });

  return { vid, uploaded: true, ...(publishResult || {}) };
};

/**
 * Đăng video lên Shopee rồi đồng bộ chỉ số (postInfo + trend) thành patch backend.
 * @param {Blob} blob
 * @param {{ caption?: string, productList?: import('@type/Shopee/Video/Creator').ShopeeVideoProduct[] }} [meta]
 * @param {(step: string) => void} [onStep]
 * @returns {Promise<import('@type/Shopee/Video/Mms').ShopeeVideoPublishSyncResult>}
 */
export const publishAndSyncVideo = async (blob, meta = {}, onStep = null) => {
  const caption = meta.caption || '';

  const result = await publishVideo(blob, {
    caption,
    productList: meta.productList || [],
  });

  if (!(result && result.uploaded)) {
    throw new Error('PUBLISH_FAILED');
  }

  if (typeof onStep === 'function') {
    onStep('videoId');
  }

  const videoId = await findPublishedVideoId(result.vid, caption);

  let infoPatch = {};

  if (videoId) {
    const detail = await getVideoDetail(videoId);
    infoPatch = buildVideoInfoPatch(detail);
  }

  return {
    vid: result.vid || '',
    videoId,
    infoPatch,
  };
};
