// @ts-nocheck

import { Buffer } from 'buffer';
import Base58 from '../../deps/Base58';
import nacl from '../../deps/nacl-fast';
import utils from '../../utils/utils';
import { createEndpoint, getBaseApi, getKeyPair } from '../../background';
import { sendDataChunksToCore } from '../../qortalRequests/get';
import { executeEvent } from '../../utils/events';

export async function reusableGet(endpoint) {
  const validApi = await getBaseApi();

  const response = await fetch(validApi + endpoint);
  const data = await response.json();
  return data;
}

async function reusablePost(endpoint, _body) {
  // const validApi = await findUsableApi();
  const url = await createEndpoint(endpoint);
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: _body,
  });
  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(errorText);
  }
  let data;
  try {
    data = await response.clone().json();
  } catch (e) {
    data = await response.text();
  }
  return data;
}

async function reusablePostStream(endpoint, _body) {
  const url = await createEndpoint(endpoint);

  const headers = {};

  const response = await fetch(url, {
    method: 'POST',
    headers,
    body: _body,
  });

  return response; // return the actual response so calling code can use response.ok
}

async function uploadChunkWithRetry(endpoint, formData, index, maxRetries = 3) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await reusablePostStream(endpoint, formData);
      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(errorText);
      }
      return; // Success
    } catch (err) {
      attempt++;
      console.warn(
        `Chunk ${index} failed (attempt ${attempt}): ${err.message}`
      );
      if (attempt >= maxRetries) {
        throw new Error(`Chunk ${index} failed after ${maxRetries} attempts`);
      }
      // Wait 10 seconds before next retry
      await new Promise((res) => setTimeout(res, 10_000));
    }
  }
}

async function resuablePostRetry(
  endpoint,
  body,
  maxRetries = 3,
  appInfo,
  resourceInfo
) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await reusablePost(endpoint, body);

      return response;
    } catch (err) {
      attempt++;
      if (attempt >= maxRetries) {
        throw new Error(
          err instanceof Error
            ? err?.message || `Failed to make request`
            : `Failed to make request`
        );
      }
      if (appInfo?.tabId && resourceInfo) {
        chrome.runtime.sendMessage({
              action: "receiveChunks",
              payload: { data:  {
              tabId: appInfo.tabId,
              publishLocation: {
              name: resourceInfo?.name,
              identifier: resourceInfo?.identifier,
              service: resourceInfo?.service,
                },
              retry: true,
              } },
          });
     
      }
      // Wait 10 seconds before next retry
      await new Promise((res) => setTimeout(res, 25_000));
    }
  }
}

export const publishData = async ({
  registeredName,
  data,
  service,
  identifier,
  uploadType,
  filename,
  withFee,
  title,
  description,
  category,
  tag1,
  tag2,
  tag3,
  tag4,
  tag5,
  feeAmount,
  sender,
  appInfo,
}: any) => {
  const validateName = async (receiverName: string) => {
    return await reusableGet(`/names/${receiverName}`);
  };

  const convertBytesForSigning = async (transactionBytesBase58: string) => {
   return await resuablePostRetry(
      '/transactions/convert',
      transactionBytesBase58,
      3,
      appInfo,
      { identifier, name: registeredName, service }
    );
  };

  const getArbitraryFee = async () => {
    const timestamp = Date.now();

    let fee = await reusableGet(
      `/transactions/unitfee?txType=ARBITRARY&timestamp=${timestamp}`
    );

    return {
      timestamp,
      fee: Number(fee),
      feeToShow: (Number(fee) / 1e8).toFixed(8),
    };
  };

  const signArbitraryWithFee = (
    arbitraryBytesBase58,
    arbitraryBytesForSigningBase58,
    keyPair
  ) => {
    if (!arbitraryBytesBase58) {
      throw new Error('ArbitraryBytesBase58 not defined');
    }

    if (!keyPair) {
      throw new Error('keyPair not defined');
    }

    const arbitraryBytes = Base58.decode(arbitraryBytesBase58);
    const _arbitraryBytesBuffer = Object.keys(arbitraryBytes).map(
      function (key) {
        return arbitraryBytes[key];
      }
    );
    const arbitraryBytesBuffer = new Uint8Array(_arbitraryBytesBuffer);
    const arbitraryBytesForSigning = Base58.decode(
      arbitraryBytesForSigningBase58
    );
    const _arbitraryBytesForSigningBuffer = Object.keys(
      arbitraryBytesForSigning
    ).map(function (key) {
      return arbitraryBytesForSigning[key];
    });
    const arbitraryBytesForSigningBuffer = new Uint8Array(
      _arbitraryBytesForSigningBuffer
    );
    const signature = nacl.sign.detached(
      arbitraryBytesForSigningBuffer,
      keyPair.privateKey
    );

    return utils.appendBuffer(arbitraryBytesBuffer, signature);
  };

  const processTransactionVersion2 = async (bytes) => {
    return await resuablePostRetry(
      '/transactions/process?apiVersion=2',
      Base58.encode(bytes),
      3,
      appInfo,
      { identifier, name: registeredName, service }
    );
  };

  const signAndProcessWithFee = async (transactionBytesBase58: string) => {
    let convertedBytesBase58 = await convertBytesForSigning(
      transactionBytesBase58
    );

    if (convertedBytesBase58.error) {
      throw new Error('Error when signing');
    }

    const resKeyPair = await getKeyPair()
    const parsedData = JSON.parse(resKeyPair)
    const uint8PrivateKey = Base58.decode(parsedData.privateKey);
    const uint8PublicKey = Base58.decode(parsedData.publicKey);
    const keyPair = {
      privateKey: uint8PrivateKey,
      publicKey: uint8PublicKey,
    };

    let signedArbitraryBytes = signArbitraryWithFee(
      transactionBytesBase58,
      convertedBytesBase58,
      keyPair
    );
    const response = await processTransactionVersion2(signedArbitraryBytes);

    let myResponse = { error: '' };

    if (response === false) {
      throw new Error('Error when signing');
    } else {
      myResponse = response;
    }
     if (appInfo?.tabId) {
    
      chrome.runtime.sendMessage({
              action: "receiveChunks",
              payload: { data:  {
              tabId: appInfo.tabId,
        publishLocation: {
          name: registeredName,
          identifier,
          service,
        },
        processed: true,
              } },
          });
    }
    return myResponse;
  };

  const validate = async () => {
    let validNameRes = await validateName(registeredName);

    if (validNameRes.error) {
      throw new Error('Name not found');
    }

    let fee = null;

    if (withFee && feeAmount) {
      fee = feeAmount;
    } else if (withFee) {
      const res = await getArbitraryFee();
      if (res.fee) {
        fee = res.fee;
      } else {
        throw new Error('unable to get fee');
      }
    }

    let transactionBytes = await uploadData(registeredName, data, fee);
    if (!transactionBytes || transactionBytes.error) {
      throw new Error(transactionBytes?.message || 'Error when uploading');
    } else if (transactionBytes.includes('Error 500 Internal Server Error')) {
      throw new Error('Error when uploading');
    }

    let signAndProcessRes;

    if (withFee) {
      signAndProcessRes = await signAndProcessWithFee(transactionBytes);
    }

    if (signAndProcessRes?.error) {
      throw new Error('Error when signing');
    }

    return signAndProcessRes;
  };

  const uploadData = async (registeredName: string, data: any, fee: number) => {
    let postBody = '';
    let urlSuffix = '';

    if (data != null) {
      if (uploadType === 'base64') {
        urlSuffix = '/base64';
      }

      if (uploadType === 'base64') {
        postBody = data;
      }
    } else {
      throw new Error('No data provided');
    }

    let uploadDataUrl = `/arbitrary/${service}/${registeredName}`;
    let paramQueries = '';
    if (identifier?.trim().length > 0) {
      uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}`;
    }

    paramQueries = paramQueries + `?fee=${fee}`;

    if (filename != null && filename != 'undefined') {
      paramQueries = paramQueries + '&filename=' + encodeURIComponent(filename);
    }

    if (title != null && title != 'undefined') {
      paramQueries = paramQueries + '&title=' + encodeURIComponent(title);
    }

    if (description != null && description != 'undefined') {
      paramQueries =
        paramQueries + '&description=' + encodeURIComponent(description);
    }

    if (category != null && category != 'undefined') {
      paramQueries = paramQueries + '&category=' + encodeURIComponent(category);
    }

    if (tag1 != null && tag1 != 'undefined') {
      paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag1);
    }

    if (tag2 != null && tag2 != 'undefined') {
      paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag2);
    }

    if (tag3 != null && tag3 != 'undefined') {
      paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag3);
    }

    if (tag4 != null && tag4 != 'undefined') {
      paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag4);
    }

    if (tag5 != null && tag5 != 'undefined') {
      paramQueries = paramQueries + '&tags=' + encodeURIComponent(tag5);
    }
    if (uploadType === 'zip') {
      paramQueries = paramQueries + '&isZip=' + true;
    }

    if (uploadType === 'base64') {
      if (urlSuffix) {
        uploadDataUrl = uploadDataUrl + urlSuffix;
      }
      uploadDataUrl = uploadDataUrl + paramQueries;
      if (appInfo?.tabId) {
      
        chrome.runtime.sendMessage({
              action: "receiveChunks",
              payload: { data:  {
              tabId: appInfo.tabId,
          publishLocation: {
            name: registeredName,
            identifier,
            service,
          },
          chunksSubmitted: 1,
          totalChunks: 1,
          processed: false,
              } },
          });
      }
      return await resuablePostRetry(uploadDataUrl, postBody, 3, appInfo, {
        identifier,
        name: registeredName,
        service,
      });
    }

    const file = data;
    // const urlCheck = `/arbitrary/check/tmp?totalSize=${file.size}`;

    // const checkEndpoint = await createEndpoint(urlCheck);
    // const checkRes = await fetch(checkEndpoint);
    // if (!checkRes.ok) {
    //   throw new Error('Not enough space on your hard drive');
    // }

    const chunkUrl = uploadDataUrl + `/chunk`;
	const createdChunkUrl = await createEndpoint(chunkUrl)
  if(sender){
    await sendDataChunksToCore(file, createdChunkUrl, sender, appInfo, {
      name: registeredName,
          identifier,
          service,
          filename: file?.name || filename || title || `${service}-${identifier || ''}`
    })

  } else {
    const chunkSize = 5 * 1024 * 1024; // 5MB

    const totalChunks = Math.ceil(file.size / chunkSize);
     if (appInfo?.tabId) {
      
       chrome.runtime.sendMessage({
              action: "receiveChunks",
              payload: { data:  {
                tabId: appInfo.tabId,
        publishLocation: {
          name: registeredName,
          identifier,
          service,
        },
        chunksSubmitted: 0,
        totalChunks,
        processed: false,
        filename:
          file?.name || filename || title || `${service}-${identifier || ''}`,
              } },
          });
    }
    for (let index = 0; index < totalChunks; index++) {
      const start = index * chunkSize;
      const end = Math.min(start + chunkSize, file.size);
      const chunk = file.slice(start, end);
      const formData = new FormData();
      formData.append('chunk', chunk, file.name); // Optional: include filename
      formData.append('index', index);

      await uploadChunkWithRetry(chunkUrl, formData, index);
       if (appInfo?.tabId) {
     
         chrome.runtime.sendMessage({
              action: "receiveChunks",
              payload: { data:  {
               tabId: appInfo.tabId,
          publishLocation: {
            name: registeredName,
            identifier,
            service,
          },
          chunksSubmitted: index + 1,
          totalChunks,
              } },
          });
      }
    }
  }

    const finalizeUrl = uploadDataUrl + `/finalize` + paramQueries;

    const finalizeEndpoint = await createEndpoint(finalizeUrl);

    const response = await fetch(finalizeEndpoint, {
      method: 'POST',
      headers: {},
    });

    if (!response?.ok) {
      const errorText = await response.text();
      throw new Error(`Finalize failed: ${errorText}`);
    }

    const result = await response.text(); // Base58-encoded unsigned transaction
    return result;
  };

  try {
    return await validate();
  } catch (error: any) {
    throw new Error(error?.message);
  }
};