// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0

import type { TezosCoinConfig, TezosConfig, TezosContext } from '../config'
import { localForger } from '@taquito/local-forging'
import { OpKind } from '@taquito/rpc'
import { createTzktApi } from '../network/tzkt'
import { createApi } from '.'

/**
 * Unrevealed mainnet account (see tzkt explorer).
 *
 * If this account is revealed, tests will fail and will need to be updated with a new one.
 * New unreveal account if necessary
 * Hash: tz1Y7xGN7jVjTTjaz6fpAeCMLiEENRkzHk4i
 * Public Key: edpkuidtssPLLHKZCo9uKDGy4nnXKeBn1Kv5h4DdWZ3d8G5tcUBy4B
 */
const UNREVEALED_SENDER = 'tz2RZHPmNVQY7h1oopsvDBMrp8i48zhc9cAL'
const UNREVEALED_PUBLIC_KEY = '0268eae382350b43ba1af0e1140e4b68dc9d2084836494554860c1be0605a7af2d'

const defaultConfig: TezosConfig = {
  baker: {
    url: 'https://baker.example.com',
  },
  explorer: {
    url: 'https://xtz-tzkt-explorer.api.vault.ledger.com',
    maxTxQuery: 100,
  },
  node: {
    url: 'https://xtz-node.api.live.ledger.com',
  },
  fees: {
    minGasLimit: 600,
    minRevealGasLimit: 300,
    minStorageLimit: 0,
    minFees: 500,
    minEstimatedFees: 500,
  },
}

/** Decoded operation. */
type DecodedOperation = {
  kind: number
  fee?: string
  gas_limit?: string
  storage_limit?: string
  delegate?: string
  source?: string
}

/**
 * Mainnet-specific integration tests
 * Please implement your tests in testnet-specific tests file (see index.integ.test.ts)
 * Use this test suite in last resort
 */
describe('Tezos Api - Mainnet', () => {
  const module = createApi()
  const context: TezosContext = {
    config: async () => defaultConfig as TezosCoinConfig,
    logger: () => undefined,
  }
  const tzktApi = createTzktApi(defaultConfig as TezosCoinConfig)

  describe('estimateFees', () => {
    it('fallback to an estimation without the public key', async () => {
      // When
      const result = await module.estimateFees(context, {
        intentType: 'transaction',
        asset: { type: 'native' },
        type: 'send',
        sender: 'tz2BHzkaizWwCmhYswwTQCycgT8mXFH8QTL5',
        senderPublicKey: '038ad002bc17d427794e148c9b9b464e977dc3cbc4d76148e97641ae86e46ec176',
        recipient: 'tz2JpHaYBjGdTq7b5mWFpG9AP3L3CckdRJcA',
        amount: BigInt(100),
      })

      expect(result.value).toBeGreaterThanOrEqual(BigInt(0))
      expect(result.parameters?.gasLimit).toBeGreaterThanOrEqual(BigInt(0))
      expect(result.parameters?.storageLimit).toBeGreaterThanOrEqual(BigInt(0))
    })
  })

  describe('encode', () => {
    it('encode a reveal operation without failing', async () => {
      // When
      const result = await module.craftTransaction(
        context,
        {
          intentType: 'transaction',
          asset: { type: 'native' },
          type: 'send',
          sender: 'tz2F4XnSd1wjwWsthemvZQjoPER7NVSt35k3',
          senderPublicKey: '03576c19462a7d0cc3d121b1b00e92258b5f71d643c99a599fc1683f03abb7a1c2',
          recipient: 'tz2TLTEWhG87pPKK7MxYLavitGGwje1znRwQ',
          amount: BigInt(500000),
        },
        { customFees: { value: BigInt(832) } }
      )

      expect(result).toEqual({ transaction: expect.stringMatching(/^([A-Fa-f0-9]{2})+$/) })
    })

    it('includes a properly estimated reveal operation when delegating from an unrevealed account with senderPublicKey', async () => {
      const sender = UNREVEALED_SENDER
      const account = await tzktApi.getAccountByAddress(sender)
      expect(account).toMatchObject({
        type: 'user',
        revealed: false,
      })

      const delegate = 'tz3Vq38qYD3GEbWcXHMLt5PaASZrkDtEiA8D'
      const intent = {
        intentType: 'transaction' as const,
        asset: { type: 'native' as const },
        type: 'delegate' as const,
        sender,
        senderPublicKey: UNREVEALED_PUBLIC_KEY,
        recipient: delegate,
        amount: BigInt(0),
      }

      const { transaction: encodedTransaction } = await module.craftTransaction(context, intent)
      const decoded = await localForger.parse(encodedTransaction.slice(2)) // strip 03 prefix

      expect(decoded.contents.length).toBe(2)

      const reveal = decoded.contents[0] as DecodedOperation
      expect(reveal.kind).toBe(OpKind.REVEAL)
      expect(BigInt(reveal.fee ?? '0')).toBeGreaterThanOrEqual(500)
      expect(Number(reveal.gas_limit ?? '0')).toBeGreaterThanOrEqual(300)
      expect(Number(reveal.storage_limit ?? '0')).toBeGreaterThanOrEqual(0)
      expect(reveal.source).toEqual(sender)

      const delegation = decoded.contents[1] as DecodedOperation
      expect(delegation.kind).toBe(OpKind.DELEGATION)
      expect(BigInt(delegation.fee ?? '0')).toBeGreaterThanOrEqual(500)
      expect(Number(delegation.gas_limit ?? '0')).toBeGreaterThanOrEqual(2500)
      expect(Number(delegation.storage_limit ?? '0')).toBeGreaterThanOrEqual(0)
      expect(delegation.delegate).toEqual(delegate)
      expect(delegation.source).toEqual(sender)

      const total = decoded.contents.reduce((sum, op) => sum + BigInt(op.fee ?? '0'), 0n)
      expect(total).toBeGreaterThanOrEqual(1000n)
    })
  })

  describe('with custom fees settings', () => {
    const contextCustomFees: TezosContext = {
      config: async () =>
        ({
          ...defaultConfig,
          fees: {
            minGasLimit: 600,
            minRevealGasLimit: 300,
            minStorageLimit: 0,
            minFees: 1200,
            minEstimatedFees: 1100,
          },
        }) as TezosCoinConfig,
      logger: () => undefined,
    }

    describe('estimateFees', () => {
      it('respects minFees on a composite transaction', async () => {
        const result = await module.estimateFees(contextCustomFees, {
          intentType: 'transaction',
          asset: { type: 'native' },
          type: 'delegate',
          sender: UNREVEALED_SENDER,
          senderPublicKey: UNREVEALED_PUBLIC_KEY,
          recipient: 'tz3Vq38qYD3GEbWcXHMLt5PaASZrkDtEiA8D',
          amount: BigInt(0),
        })

        // minFees should be used for both operations, so total should be minFees * 2
        expect(result.value).toBe(2400n)
      })

      describe('craftTransaction', () => {
        it('respects customFee on a composite transaction if greater than default estimation', async () => {
          const { transaction: encodedTransaction } = await module.craftTransaction(
            contextCustomFees,
            {
              intentType: 'transaction',
              asset: { type: 'native' },
              type: 'delegate',
              sender: UNREVEALED_SENDER,
              senderPublicKey: UNREVEALED_PUBLIC_KEY,
              recipient: 'tz3Vq38qYD3GEbWcXHMLt5PaASZrkDtEiA8D',
              amount: BigInt(0),
            },
            { customFees: { value: 3000n } }
          )
          const decoded = await localForger.parse(encodedTransaction.slice(2))

          const reveal = decoded.contents[0] as DecodedOperation
          expect(reveal.kind).toBe(OpKind.REVEAL)
          expect(BigInt(reveal.fee ?? '0')).toEqual(1200n)

          const delegation = decoded.contents[1] as DecodedOperation
          expect(delegation.kind).toBe(OpKind.DELEGATION)
          expect(BigInt(delegation.fee ?? '0')).toBeGreaterThanOrEqual(1800n)

          // The provided customFee exceeds the default estimation. In this case, we expect the fee to be increased on
          // the main operation (delegate) so that total fee paid respects customFee.
          const totalFees = decoded.contents.reduce((sum, op) => sum + BigInt(op.fee ?? '0'), 0n)
          expect(totalFees).toBe(3000n)
        })

        it('respects customFee on a composite transaction if lesser than default estimation', async () => {
          const { transaction: encodedTransaction } = await module.craftTransaction(
            contextCustomFees,
            {
              intentType: 'transaction',
              asset: { type: 'native' },
              type: 'delegate',
              sender: 'tz1Y7xGN7jVjTTjaz6fpAeCMLiEENRkzHk4i',
              senderPublicKey: 'edpkuidtssPLLHKZCo9uKDGy4nnXKeBn1Kv5h4DdWZ3d8G5tcUBy4B',
              recipient: 'tz3Vq38qYD3GEbWcXHMLt5PaASZrkDtEiA8D',
              amount: BigInt(0),
            },
            { customFees: { value: 2000n } }
          )
          const decoded = await localForger.parse(encodedTransaction.slice(2))

          const reveal = decoded.contents[0] as DecodedOperation
          expect(reveal.kind).toBe(OpKind.REVEAL)
          expect(BigInt(reveal.fee ?? '0')).toEqual(1200n)

          const delegation = decoded.contents[1] as DecodedOperation
          expect(delegation.kind).toBe(OpKind.DELEGATION)
          expect(BigInt(delegation.fee ?? '0')).toBeGreaterThanOrEqual(800n)

          // The provided customFee is less than the default estimation. In this case, we expect the fee to be decreased
          // on the main operation (delegate) so that total fee paid respects customFee. Transaction could fail, but
          // that's caller responsibility.
          const totalFees = decoded.contents.reduce((sum, op) => sum + BigInt(op.fee ?? '0'), 0n)
          expect(totalFees).toBe(2000n)
        })
      })
    })
  })
})
