import {
  getNWCData,
  getSupportedMethods,
  isWithinNWCBalanceTimeFrame,
  splitAndStoreNWCData,
} from '.';
import {
  decryptMessage,
  encriptMessage,
} from '../messaging/encodingAndDecodingMessages';
import { publishToSingleRelay } from './publishResponse';
import {
  getNWCLightningReceiveRequest,
  getNWCSparkBalance,
  getNWCSparkTransactions,
  initializeNWCWallet,
  NWCSparkLightningPaymentStatus,
  nwcWallet,
  sendNWCSparkLightningPayment,
} from './wallet';
import bolt11 from 'bolt11';
import { getSparkPaymentStatus, sparkPaymentType } from '../spark';
import { pushInstantNotification } from '../notifications';
import NWCInvoiceManager from './cachedNWCTxs';
import { NOSTR_RELAY_URL, NWC_IDENTITY_PUB_KEY } from '../../constants';
import { finalizeEvent } from 'nostr-tools';
import { getFunctions } from '@react-native-firebase/functions';
import { Platform } from 'react-native';
import fetchBackend from '../../../db/handleBackend';
import { getLocalStorageItem } from '../localStorage';
import sha256Hash from '../hash';
import { transformTxToPaymentObject } from '../spark/transformTxToPayment';

const handledEventIds = new Set();
let nwcAccounts, fullStorageObject;
let walletInitializationPromise = null;

const RELAY_URL = NOSTR_RELAY_URL;

const ERROR_CODES = {
  INTERNAL: 'INTERNAL',
  RESTRICTED: 'RESTRICTED',
  QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',
};

const createErrorResponse = (method, code, message) => ({
  result_type: method,
  error: { code, message },
});

const ensureWalletConnection = async () => {
  if (nwcWallet) {
    return { isConnected: true };
  }

  if (walletInitializationPromise) {
    console.log('Wallet initialization already in progress, waiting...');
    return await walletInitializationPromise;
  }

  walletInitializationPromise = initializeNWCWallet();

  try {
    const result = await walletInitializationPromise;
    // Clear the promise on successful completion
    walletInitializationPromise = null;
    return result;
  } catch (error) {
    // Clear the promise on error so retry is possible
    walletInitializationPromise = null;
    throw error;
  }
};

const handleGetInfo = selectedNWCAccount => ({
  result_type: 'get_info',
  result: {
    alias: 'N/A',
    color: 'N/A',
    pubkey: 'N/A',
    network: 'mainnet',
    block_height: 1,
    block_hash: 'N/A',
    methods: getSupportedMethods(selectedNWCAccount.permissions),
  },
});

const handleGetTransactions = async requestParams => {
  const connectResponse = await ensureWalletConnection();
  if (!connectResponse.isConnected) {
    return createErrorResponse(
      'list_transactions',
      ERROR_CODES.INTERNAL,
      'Unable to connect to wallet',
    );
  }

  const { from, until, limit = 20, offset = 0, type } = requestParams;

  let allTransactions = [];
  let currentOffset = 0;
  const chunkSize = limit * 1.5;
  let hasMore = true;

  while (hasMore) {
    const chunk = await getNWCSparkTransactions(chunkSize, currentOffset);

    if (!chunk || chunk.transfers.length === 0) {
      hasMore = false;
      break;
    }

    allTransactions = allTransactions.concat(chunk.transfers);
    currentOffset += chunkSize;

    // Stop fetching if we have enough for this request (with buffer for filtering)
    if (allTransactions.length >= offset + limit) {
      break;
    }

    // Stop if we got less than requested (end of data)
    if (chunk.transfers.length < chunkSize) {
      hasMore = false;
    }
  }

  let filteredTransactions = allTransactions.filter(tx => {
    // Filter by timestamp range if provided
    const type = sparkPaymentType(tx);

    if (type === 'spark') return false;
    if (from || until) {
      const txTime = tx.createdTime
        ? new Date(tx.createdTime).getTime() / 1000
        : null;
      if (!txTime) return false;

      if (from && txTime < from) return false;
      if (until && txTime > until) return false;
    }

    // Filter by transaction type if specified
    if (type) {
      const isIncoming = tx.transferDirection === 'INCOMING';
      const isOutgoing = tx.transferDirection === 'OUTGOING';

      if (type === 'incoming' && !isIncoming) return false;
      if (type === 'outgoing' && !isOutgoing) return false;
    }

    return true;
  });

  const paginatedTransactions = filteredTransactions.slice(
    offset,
    offset + limit,
  );

  const formatted = await Promise.all(
    paginatedTransactions.map(async tx => {
      const transformedObjct = await transformTxToPaymentObject(
        tx,
        undefined,
        undefined,
        false,
        [],
        undefined,
        1,
      );

      return {
        type: tx.transferDirection?.toLowerCase(),
        invoice: transformedObjct.details.address,
        description: transformedObjct.details.description,
        description_hash: null,
        preimage: transformedObjct.details.preimage,
        payment_hash: sha256Hash(transformedObjct.details.preimage),
        amount: tx.totalValue * 1000,
        fees_paid: transformedObjct.details.fee,
        created_at: tx.createdTime
          ? Math.floor(new Date(tx.createdTime).getTime() / 1000)
          : null,
        settled_at: tx.expiryTime
          ? Math.floor(new Date(tx.updatedTime).getTime() / 1000)
          : null,
        metadata: {},
      };
    }),
  );

  return {
    result_type: 'list_transactions',
    result: {
      transactions: formatted,
    },
  };
};

const handleMakeInvoice = async (
  requestParams,
  selectedNWCAccount,
  fullStorageObject,
  event,
) => {
  const sparkPubKey = JSON.parse(
    await getLocalStorageItem(NWC_IDENTITY_PUB_KEY),
  );

  if (!sparkPubKey) {
    return createErrorResponse(
      'make_invoice',
      ERROR_CODES.INTERNAL,
      'Unable to connect to wallet',
    );
  }

  await fetchBackend(
    'handleNWCEvent',
    {
      requestMethod: 'make_invoice',
      amount: Math.round(requestParams.amount / 1000),
      description: requestParams.description,
      expiry: requestParams.expiry,
      publicKey: selectedNWCAccount.publicKey,
      privateKey: selectedNWCAccount.privateKey,
      sparkIdentityPubKey: sparkPubKey,
      event: { clientPubKey: event.clientPubKey, id: event.id },
    },
    selectedNWCAccount.privateKey,
    selectedNWCAccount.publicKey,
  );
  // await splitAndStoreNWCData({
  //   ...fullStorageObject,
  //   accounts: {
  //     ...fullStorageObject.accounts,
  //     [selectedNWCAccount.publicKey]: {
  //       ...selectedNWCAccount,
  //       shouldGetNewBalance: true,
  //     },
  //   },
  // });
};

const handleLookupInvoice = async (
  requestParams,
  event,
  selectedNWCAccount,
) => {
  let foundInvoice = null;
  const { payment_hash, invoice } = requestParams;
  try {
    foundInvoice = await NWCInvoiceManager.handleLookupInvoice(requestParams);
  } catch (err) {
    console.log('Error handling lookup', err);
    return createErrorResponse(
      'lookup_invoice',
      ERROR_CODES.INTERNAL,
      err.message,
    );
  }

  if (foundInvoice) {
    const { sparkID, ...invoiceWithoutSparkID } = foundInvoice;
    if (invoiceWithoutSparkID.status !== 'pending') {
      return {
        result_type: 'lookup_invoice',
        result: invoiceWithoutSparkID,
      };
    }

    const connectResponse = await ensureWalletConnection();
    if (!connectResponse.isConnected) {
      return createErrorResponse(
        'lookup_invoice',
        ERROR_CODES.INTERNAL,
        'Unable to connect to wallet',
      );
    }

    let sparkPaymentResponse;
    if (invoiceWithoutSparkID.type === 'INCOMING') {
      sparkPaymentResponse = await getNWCLightningReceiveRequest(sparkID);
    } else {
      sparkPaymentResponse = await NWCSparkLightningPaymentStatus(sparkID);
    }

    if (!sparkPaymentResponse.didWork)
      return createErrorResponse(
        'lookup_invoice',
        ERROR_CODES.INTERNAL,
        'Unable to lookup invoice.',
      );
    const data = sparkPaymentResponse.paymentResponse;
    const status = getSparkPaymentStatus(data.status);

    if (status !== 'pending') {
      await NWCInvoiceManager.markInvoiceAsNotPending(
        invoiceWithoutSparkID.payment_hash,
        status,
        data.paymentPreimage,
      );
      return {
        result_type: 'lookup_invoice',
        result: {
          ...invoiceWithoutSparkID,
          status: status,
          preimage: data.paymentPreimage || '',
          settled_at: Date.now(),
        },
      };
    }
    return {
      result_type: 'lookup_invoice',
      result: invoiceWithoutSparkID,
    };
  }

  await fetchBackend(
    'handleNWCEvent',
    {
      requestMethod: 'lookup_invoice',
      invoice: invoice,
      payment_hash: payment_hash,
      publicKey: selectedNWCAccount.publicKey,
      privateKey: selectedNWCAccount.privateKey,
      event: { clientPubKey: event.clientPubKey, id: event.id },
    },
    selectedNWCAccount.privateKey,
    selectedNWCAccount.publicKey,
  );
};

const handlePayInvoice = async (
  requestParams,
  selectedNWCAccount,
  fullStorageObject,
) => {
  const connectResponse = await ensureWalletConnection();

  const decoded = bolt11.decode(requestParams.invoice);
  const paymentAmount = Math.round(decoded.millisatoshis / 1000);
  const timeFrame = isWithinNWCBalanceTimeFrame(
    selectedNWCAccount.budgetRenewalSettings.option,
    selectedNWCAccount.lastRotated,
  );

  if (
    selectedNWCAccount.budgetRenewalSettings.amount !== 'Unlimited' &&
    selectedNWCAccount.budgetRenewalSettings.amount <
      selectedNWCAccount.totalSent + paymentAmount &&
    timeFrame
  ) {
    return createErrorResponse(
      'pay_invoice',
      ERROR_CODES.QUOTA_EXCEEDED,
      'The wallet has exceeded its spending quota.',
    );
  } else if (!timeFrame) {
    await splitAndStoreNWCData({
      ...fullStorageObject,
      accounts: {
        ...fullStorageObject.accounts,
        [selectedNWCAccount.publicKey]: {
          ...selectedNWCAccount,
          lastRotated: new Date().getTime(),
          totalSent: 0,
        },
      },
    });
  }

  if (!connectResponse.isConnected) {
    // const currentExpense
    return createErrorResponse(
      'pay_invoice',
      ERROR_CODES.INTERNAL,
      'Unable to connect to wallet',
    );
  }

  const invoice = await sendNWCSparkLightningPayment({
    invoice: requestParams.invoice,
  });

  if (!invoice.didWork) {
    return createErrorResponse(
      'pay_invoice',
      ERROR_CODES.INTERNAL,
      'Unable to send payment',
    );
  }

  const response = invoice.paymentResponse;
  await new Promise(res => setTimeout(res, 1000));

  const status = await NWCSparkLightningPaymentStatus(response.id);

  await NWCInvoiceManager.storeCreatedInvoice({
    payment_hash: sha256Hash(status?.paymentResponse?.paymentPreimage || ''),
    invoice: response.encodedInvoice,
    amount: paymentAmount,
    fee: Math.round(response.fee.originalValue / 1000),
    description: '',
    status: getSparkPaymentStatus(status?.paymentResponse.status),
    created_at: response.createdAt,
    sparkID: response.id,
    type: 'OUTGOING',
    preimage: status?.paymentResponse?.paymentPreimage || '',
  });

  if (!status.didWork) {
    return createErrorResponse(
      'pay_invoice',
      ERROR_CODES.INTERNAL,
      'Unable to retrieve payment status',
    );
  }

  // await splitAndStoreNWCData({
  //   ...fullStorageObject,
  //   accounts: {
  //     ...fullStorageObject.accounts,
  //     [selectedNWCAccount.publicKey]: {
  //       ...selectedNWCAccount,
  //       shouldGetNewBalance: true,
  //     },
  //   },
  // });

  return {
    result_type: 'pay_invoice',
    result: {
      preimage: status.paymentResponse.paymentPreimage || '',
    },
  };
};

const handleGetBalance = async (selectedNWCAccount, fullStorageObject) => {
  console.log('running in get balance');
  // if (
  //   // !selectedNWCAccount.shouldGetNewBalance &&
  //   // selectedNWCAccount.walletBalance &&
  //   // selectedNWCAccount.lastChecked - Date.now() < 60 * 5
  // ) {
  //   console.log('running cached wallet balance');
  //   return {
  //     result_type: 'get_balance',
  //     result: {
  //       balance: Number(selectedNWCAccount.walletBalance) * 1000,
  //     },
  //   };
  // }
  const connectResponse = await ensureWalletConnection();
  console.log('conection response');
  if (!connectResponse.isConnected) {
    return createErrorResponse(
      'get_balance',
      ERROR_CODES.INTERNAL,
      'Unable to connect to wallet',
    );
  }

  const balance = await getNWCSparkBalance();

  // await splitAndStoreNWCData({
  //   ...fullStorageObject,
  //   accounts: {
  //     ...fullStorageObject.accounts,
  //     [selectedNWCAccount.publicKey]: {
  //       ...selectedNWCAccount,
  //       shouldGetNewBalance: false,
  //       walletBalance: Number(balance.balance),
  //       lastChecked: Date.now(),
  //     },
  //   },
  // });
  return {
    result_type: 'get_balance',
    result: {
      balance: Number(balance.balance) * 1000,
    },
  };
};

const processEvent = async (event, selectedNWCAccount) => {
  const { requestMethod, requestParams } = event;

  console.log('request method', requestMethod);
  console.log('request params', requestParams);

  let returnObject;

  switch (requestMethod) {
    case 'get_info':
      returnObject = handleGetInfo(selectedNWCAccount);
      break;

    case 'list_transactions':
      if (!selectedNWCAccount.permissions.transactionHistory) {
        returnObject = createErrorResponse(
          requestMethod,
          ERROR_CODES.RESTRICTED,
          'Requested service is not authorized',
        );
        break;
      }
      returnObject = await handleGetTransactions(requestParams);
      break;
    case 'make_invoice':
      if (!selectedNWCAccount.permissions.receivePayments) {
        returnObject = createErrorResponse(
          requestMethod,
          ERROR_CODES.RESTRICTED,
          'Requested service is not authorized',
        );
        break;
      }
      await handleMakeInvoice(
        requestParams,
        selectedNWCAccount,
        fullStorageObject,
        event,
      );
      break;
    case 'lookup_invoice':
      if (!selectedNWCAccount.permissions.lookupInvoice) {
        returnObject = createErrorResponse(
          requestMethod,
          ERROR_CODES.RESTRICTED,
          'Requested service is not authorized',
        );
        break;
      }
      await handleLookupInvoice(requestParams, event, selectedNWCAccount);
      break;
    case 'pay_invoice':
      if (!selectedNWCAccount.permissions.sendPayments) {
        returnObject = createErrorResponse(
          requestMethod,
          ERROR_CODES.RESTRICTED,
          'Requested service is not authorized',
        );
        break;
      }
      returnObject = await handlePayInvoice(
        requestParams,
        selectedNWCAccount,
        fullStorageObject,
      );
      break;

    case 'get_balance':
      if (!selectedNWCAccount.permissions.getBalance) {
        returnObject = createErrorResponse(
          requestMethod,
          ERROR_CODES.RESTRICTED,
          'Requested service is not authorized',
        );
        break;
      }
      returnObject = await handleGetBalance(
        selectedNWCAccount,
        fullStorageObject,
      );
      break;

    default:
      returnObject = createErrorResponse(
        requestMethod,
        ERROR_CODES.RESTRICTED,
        'Requested service is not authorized',
      );
  }

  if (typeof returnObject !== 'object' || returnObject === null) {
    console.log('Invalid return object from event handler:', returnObject);
    return;
  }

  return returnObject;
};

function decryptEventMessage(selectedNWCAccount, event) {
  const decryptedContent = decryptMessage(
    selectedNWCAccount.privateKey,
    event.clientPubKey,
    event.content,
  );

  const data = JSON.parse(decryptedContent);
  return data;
}

export default async function handleNWCBackgroundEvent(notificationData) {
  try {
    // Initialize accounts if not already done
    if (__DEV__) {
      getFunctions().useEmulator(process.env.DEVICE_IP, 5001);
    }
    let {
      data: { body: nwcEvent },
    } = notificationData;
    console.log('background nwc event', nwcEvent);
    if (!nwcEvent) return;

    try {
      nwcEvent = JSON.parse(nwcEvent);
    } catch (err) {}

    // // Filter out already handled events upfront
    const newEvents = nwcEvent?.events;
    if (!newEvents) return;

    fullStorageObject = await getNWCData();
    nwcAccounts = fullStorageObject.accounts;

    pushInstantNotification(
      `Received ${newEvents.length} event${newEvents.length === 1 ? '' : 's'}`,
      'Nostr Connect',
    );

    const filteredEvents = newEvents
      .map((event, index) => {
        const selectedNWCAccount = nwcAccounts[event.pubkey];
        const parsedData = decryptEventMessage(selectedNWCAccount, event);

        const { method: requestMethod, params: requestParams } = parsedData;
        const handledKey = `${event.clientPubKey}-${requestMethod}`;

        if (handledEventIds.has(handledKey)) return false;
        handledEventIds.add(handledKey);
        return { requestMethod, requestParams, ...event, content: parsedData };
      })
      .filter(Boolean);

    await filteredEvents.forEach(async (event, index) => {
      const selectedNWCAccount = nwcAccounts[event.pubkey];
      console.log(selectedNWCAccount, 'SELECTED NWC ACCOUNT');
      if (!selectedNWCAccount) return null;

      try {
        const returnObject = await processEvent(event, selectedNWCAccount);
        if (!returnObject) return;

        const eventTemplate = {
          kind: 23195,
          created_at: Math.floor(Date.now() / 1000),
          tags: [
            ['p', event.clientPubKey],
            ['e', event.id],
          ],
          content: encriptMessage(
            selectedNWCAccount.privateKey,
            event.clientPubKey,
            JSON.stringify(returnObject),
          ),
        };

        const finalizedEvent = finalizeEvent(
          eventTemplate,
          Buffer.from(selectedNWCAccount.privateKey, 'hex'),
        );

        await publishToSingleRelay([finalizedEvent], RELAY_URL);
      } catch (error) {
        console.error('Error processing event:', event.id, error);
        return null;
      }
    });
    handledEventIds.clear();
  } catch (err) {
    console.error('Error handling background nwc event', err);
  }
}
