import { buildApiServer } from '../../src/api/init';
import { Brc20TokenResponse, Brc20ActivityResponse } from '../../src/api/schemas';
import { Brc20PgStore } from '../../src/pg/brc20/brc20-pg-store';
import { PgStore } from '../../src/pg/pg-store';
import {
  TestFastifyServer,
  ORDINALS_MIGRATIONS_DIR,
  BRC20_MIGRATIONS_DIR,
  clearDb,
  incrementing,
  randomHash,
  runMigrations,
  brc20TokenDeploy,
  brc20Operation,
} from '../helpers';

describe('BRC-20 API', () => {
  let db: PgStore;
  let brc20Db: Brc20PgStore;
  let fastify: TestFastifyServer;

  beforeEach(async () => {
    db = await PgStore.connect();
    await runMigrations(db.sql, ORDINALS_MIGRATIONS_DIR);
    brc20Db = await Brc20PgStore.connect();
    await runMigrations(brc20Db.sql, BRC20_MIGRATIONS_DIR);
    fastify = await buildApiServer({ db, brc20Db });
  });

  afterEach(async () => {
    await fastify.close();
    await clearDb(db.sql);
    await db.close();
    await clearDb(brc20Db.sql);
    await brc20Db.close();
  });

  describe('/brc-20/tokens', () => {
    test('tokens endpoint', async () => {
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc:0',
        offset: '0',
        to_address: null,
        amount: '0',
      });
      const response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/tokens/pepe`,
      });
      expect(response.statusCode).toBe(200);
      expect(response.json()).toStrictEqual({
        token: {
          id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dci0',
          number: 0,
          block_height: 767430,
          tx_id: '38c46a8bf7ec90bc7f6b797e7dc84baa97f4e5fd4286b92fe1b50176d03b18dc',
          address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
          ticker: 'pepe',
          max_supply: '21000000.000000000000000000',
          mint_limit: '21000000.000000000000000000',
          decimals: 18,
          deploy_timestamp: 1677803510000,
          minted_supply: '0.000000000000000000',
          tx_count: 1,
          self_mint: false,
        },
        supply: {
          max_supply: '21000000.000000000000000000',
          minted_supply: '0.000000000000000000',
          holders: 0,
        },
      });
    });

    test('tokens filter by ticker prefix', async () => {
      let transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'peer',
        display_ticker: 'peer',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'abcd',
        display_ticker: 'abcd',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'dcba',
        display_ticker: 'dcba',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });
      const response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/tokens?ticker=PE&ticker=AB`,
      });
      expect(response.statusCode).toBe(200);
      const responseJson = response.json();
      expect(responseJson.total).toBe(3);
      expect(responseJson.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({ ticker: 'pepe' }),
          expect.objectContaining({ ticker: 'peer' }),
          expect.objectContaining({ ticker: 'abcd' }),
        ])
      );
    });

    test('tokens using order_by tx_count', async () => {
      let transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 10,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'peer',
        display_ticker: 'peer',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 8,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'abcd',
        display_ticker: 'abcd',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      transferHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'dcba',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: '0',
        block_height: '767430',
        block_hash: '00000000000000000000a8c86d8777fd891d07308b13cf03680300636ab10fa6',
        tx_id: transferHash,
        tx_index: 0,
        address: 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td',
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 6,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });
      const response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/tokens?order_by=tx_count`,
      });
      expect(response.statusCode).toBe(200);
      const responseJson = response.json();
      expect(responseJson.total).toBe(4);
      expect(responseJson.results[0].ticker).toBe('pepe');
      expect(responseJson.results[1].ticker).toBe('peer');
      expect(responseJson.results[2].ticker).toBe('dcba');
      expect(responseJson.results[3].ticker).toBe('abcd');
    });
  });

  describe('/brc-20/activity', () => {
    test('activity for token transfers', async () => {
      // Setup
      const blockHeights = incrementing(767430);
      const numbers = incrementing(0);
      const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz';
      const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4';

      // A deploys pepe
      let transferHash = randomHash();
      let blockHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: numbers.next().value.toString(),
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        address: addressA,
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      // Verify that the pepe deploy is in the activity feed
      let response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      let json = response.json();
      expect(json.total).toBe(1);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'deploy',
            ticker: 'pepe',
            address: addressA,
            deploy: expect.objectContaining({
              max_supply: '21000000.000000000000000000',
            }),
          } as Brc20ActivityResponse),
        ])
      );

      // A mints 10000 pepe
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'mint',
        inscription_id: `${transferHash}i0`,
        inscription_number: numbers.next().value.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressA,
        to_address: null,
        amount: '10000000000000000000000',
      });

      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(2);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'deploy',
            ticker: 'pepe',
          } as Brc20ActivityResponse),
          expect.objectContaining({
            operation: 'mint',
            ticker: 'pepe',
            address: addressA,
            mint: {
              amount: '10000.000000000000000000',
            },
          } as Brc20ActivityResponse),
        ])
      );

      // B mints 10000 pepe
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'mint',
        inscription_id: `${transferHash}i0`,
        inscription_number: numbers.next().value.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressB,
        to_address: null,
        amount: '10000000000000000000000',
      });

      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(3);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'mint',
            ticker: 'pepe',
            address: addressB,
            mint: {
              amount: '10000.000000000000000000',
            },
          } as Brc20ActivityResponse),
        ])
      );

      // A creates transfer of 9000 pepe
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer',
        inscription_id: `${transferHash}i0`,
        inscription_number: numbers.next().value.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressA,
        to_address: null,
        amount: '9000000000000000000000',
      });

      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(4);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer',
            ticker: 'pepe',
            address: addressA,
            tx_id: transferHash,
            transfer: {
              amount: '9000.000000000000000000',
              from_address: addressA,
            },
          } as Brc20ActivityResponse),
        ])
      );

      // A sends transfer inscription to B (aka transfer/sale)
      transferHash = randomHash();
      blockHash = randomHash();
      const blockHeight = blockHeights.next().value.toString();
      const number = numbers.next().value.toString();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer_send',
        inscription_id: `${transferHash}i0`,
        inscription_number: number,
        ordinal_number: '200000',
        block_height: blockHeight,
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressA,
        to_address: addressB,
        amount: '9000000000000000000000',
      });
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer_receive',
        inscription_id: `${transferHash}i0`,
        inscription_number: number,
        ordinal_number: '200000',
        block_height: blockHeight,
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressB,
        to_address: null,
        amount: '9000000000000000000000',
      });

      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(5);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressB,
            transfer_send: {
              amount: '9000.000000000000000000',
              from_address: addressA,
              to_address: addressB,
            },
          } as Brc20ActivityResponse),
        ])
      );

      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe&operation=transfer_send`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(1);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressB,
            transfer_send: {
              amount: '9000.000000000000000000',
              from_address: addressA,
              to_address: addressB,
            },
          } as Brc20ActivityResponse),
        ])
      );
    });

    test('activity for multiple token transfers among three participants', async () => {
      // Step 1: A deploys a token
      // Step 2: A mints 1000 of the token
      // Step 3: B mints 2000 of the token
      // Step 4: A creates a transfer to B
      // Step 5: B creates a transfer to C
      // Step 6: A transfer_send the transfer to B
      // Step 7: B transfer_send the transfer to C

      // Setup
      const inscriptionNumbers = incrementing(0);
      const blockHeights = incrementing(767430);
      const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz';
      const addressB = 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4';
      const addressC = 'bc1q9d80h0q5d3f54w7w8c3l2sguf9uset4ydw9xj2';

      // Step 1: A deploys a token
      let number = inscriptionNumbers.next().value;
      let transferHash = randomHash();
      let blockHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        address: addressA,
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      // Verify that the pepe deploy is in the activity feed
      let response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      let json = response.json();
      expect(json.total).toBe(1);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'deploy',
            ticker: 'pepe',
            address: addressA,
            deploy: expect.objectContaining({
              max_supply: '21000000.000000000000000000',
            }),
          } as Brc20ActivityResponse),
        ])
      );

      // Step 2: A mints 1000 of the token
      number = inscriptionNumbers.next().value;
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'mint',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressA,
        to_address: null,
        amount: '1000000000000000000000',
      });

      // Verify that the pepe mint is in the activity feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(2);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'mint',
            ticker: 'pepe',
            address: addressA,
            mint: {
              amount: '1000.000000000000000000',
            },
          } as Brc20ActivityResponse),
        ])
      );
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe&address=${addressA}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(2);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'deploy',
            ticker: 'pepe',
            address: addressA,
            deploy: expect.objectContaining({
              max_supply: '21000000.000000000000000000',
            }),
          } as Brc20ActivityResponse),
          expect.objectContaining({
            operation: 'mint',
            ticker: 'pepe',
            address: addressA,
            mint: {
              amount: '1000.000000000000000000',
            },
          } as Brc20ActivityResponse),
        ])
      );

      // Step 3: B mints 2000 of the token
      number = inscriptionNumbers.next().value;
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'mint',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressB,
        to_address: null,
        amount: '2000000000000000000000',
      });

      // Verify that the pepe mint is in the activity feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(3);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'mint',
            ticker: 'pepe',
            address: addressB,
            mint: {
              amount: '2000.000000000000000000',
            },
          } as Brc20ActivityResponse),
        ])
      );

      // Step 4: A creates a transfer to B
      const numberAB = inscriptionNumbers.next().value;
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer',
        inscription_id: `${transferHash}i0`,
        inscription_number: numberAB.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressA,
        to_address: null,
        amount: '1000000000000000000000',
      });

      // Verify that the pepe transfer is in the activity feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(4);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer',
            ticker: 'pepe',
            address: addressA,
            tx_id: transferHash,
            transfer: {
              amount: '1000.000000000000000000',
              from_address: addressA,
            },
          } as Brc20ActivityResponse),
        ])
      );
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe&address=${addressA}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(3);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer',
            ticker: 'pepe',
            address: addressA,
            tx_id: transferHash,
            transfer: {
              amount: '1000.000000000000000000',
              from_address: addressA,
            },
          } as Brc20ActivityResponse),
        ])
      );

      // Step 5: B creates a transfer to C
      const numberBC = inscriptionNumbers.next().value;
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer',
        inscription_id: `${transferHash}i0`,
        inscription_number: numberBC.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressB,
        to_address: null,
        amount: '2000000000000000000000',
      });

      // Verify that the pepe transfer is in the activity feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(5);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer',
            ticker: 'pepe',
            address: addressB,
            tx_id: transferHash,
            transfer: {
              amount: '2000.000000000000000000',
              from_address: addressB,
            },
          } as Brc20ActivityResponse),
        ])
      );

      // Step 6: A transfer_send the transfer to B
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer_send',
        inscription_id: `${transferHash}i0`,
        inscription_number: numberAB.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressA,
        to_address: addressB,
        amount: '1000000000000000000000',
      });
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer_receive',
        inscription_id: `${transferHash}i0`,
        inscription_number: numberAB.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressB,
        to_address: null,
        amount: '1000000000000000000000',
      });
      // A gets the transfer send in its feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe&address=${addressA}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(4);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressB,
            transfer_send: {
              amount: '1000.000000000000000000',
              from_address: addressA,
              to_address: addressB,
            },
          } as Brc20ActivityResponse),
        ])
      );
      // B gets the transfer send in its feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe&address=${addressB}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(3);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressB,
            transfer_send: {
              amount: '1000.000000000000000000',
              from_address: addressA,
              to_address: addressB,
            },
          } as Brc20ActivityResponse),
        ])
      );

      // Verify that the pepe transfer_send is in the activity feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(6);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressB,
            transfer_send: {
              amount: '1000.000000000000000000',
              from_address: addressA,
              to_address: addressB,
            },
          } as Brc20ActivityResponse),
        ])
      );

      // Step 7: B transfer_send the transfer to C
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer_send',
        inscription_id: `${transferHash}i0`,
        inscription_number: numberBC.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressB,
        to_address: addressC,
        amount: '2000000000000000000000',
      });
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'transfer_receive',
        inscription_id: `${transferHash}i0`,
        inscription_number: numberBC.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressC,
        to_address: null,
        amount: '2000000000000000000000',
      });

      // Verify that the pepe transfer_send is in the activity feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(7);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressC,
            transfer_send: {
              amount: '2000.000000000000000000',
              from_address: addressB,
              to_address: addressC,
            },
          } as Brc20ActivityResponse),
        ])
      );
      // B gets the transfer send in its feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe&address=${addressB}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(4);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressC,
            transfer_send: {
              amount: '2000.000000000000000000',
              from_address: addressB,
              to_address: addressC,
            },
          } as Brc20ActivityResponse),
        ])
      );
      // C gets the transfer send in its feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=pepe&address=${addressC}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(1);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'transfer_send',
            ticker: 'pepe',
            tx_id: transferHash,
            address: addressC,
            transfer_send: {
              amount: '2000.000000000000000000',
              from_address: addressB,
              to_address: addressC,
            },
          } as Brc20ActivityResponse),
        ])
      );
    });

    test('activity for multiple token creation', async () => {
      const inscriptionNumbers = incrementing(0);
      const blockHeights = incrementing(767430);
      const addressA = 'bc1q6uwuet65rm6xvlz7ztw2gvdmmay5uaycu03mqz';

      // Step 1: Create a token pepe
      let number = inscriptionNumbers.next().value;
      let transferHash = randomHash();
      let blockHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        address: addressA,
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      // Verify that the pepe deploy is in the activity feed
      let response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity`,
      });
      expect(response.statusCode).toBe(200);
      let json = response.json();
      expect(json.total).toBe(1);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'deploy',
            ticker: 'pepe',
            address: addressA,
            deploy: expect.objectContaining({
              max_supply: '21000000.000000000000000000',
            }),
          } as Brc20ActivityResponse),
        ])
      );

      // Step 2: Create a token peer
      number = inscriptionNumbers.next().value;
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'peer',
        display_ticker: 'peer',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        address: addressA,
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      // Verify that the peer deploy is in the activity feed
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(2);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'deploy',
            ticker: 'peer',
            address: addressA,
            deploy: expect.objectContaining({
              max_supply: '21000000.000000000000000000',
            }),
          } as Brc20ActivityResponse),
        ])
      );

      // Verify that no events are available before the first block height
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?ticker=peer&block_height=${767430}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(0);
      expect(json.results).toEqual([]);

      // Verify that the peer deploy is not in the activity feed when using block_height parameter
      response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/activity?block_height=${767430}`,
      });
      expect(response.statusCode).toBe(200);
      json = response.json();
      expect(json.total).toBe(1);
      expect(json.results).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            operation: 'deploy',
            ticker: 'pepe',
            address: addressA,
            deploy: expect.objectContaining({
              max_supply: '21000000.000000000000000000',
            }),
          } as Brc20ActivityResponse),
        ])
      );
      // Should NOT include peer at this block height
      expect(json.results).not.toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            ticker: 'peer',
          } as Brc20ActivityResponse),
        ])
      );
    });
  });

  describe('/brc-20/token/holders', () => {
    test('displays holders for token', async () => {
      const inscriptionNumbers = incrementing(0);
      const blockHeights = incrementing(767430);
      const addressA = 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td';
      const addressB = 'bc1qp9jgp9qtlhgvwjnxclj6kav6nr2fq09c206pyl';

      let number = inscriptionNumbers.next().value;
      let transferHash = randomHash();
      let blockHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        address: addressA,
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });

      number = inscriptionNumbers.next().value;
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'mint',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressA,
        to_address: null,
        amount: '10000000000000000000000',
      });

      number = inscriptionNumbers.next().value;
      transferHash = randomHash();
      blockHash = randomHash();
      await brc20Operation(brc20Db.sql, {
        ticker: 'pepe',
        operation: 'mint',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        ordinal_number: '200000',
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        output: `${transferHash}:0`,
        offset: '0',
        timestamp: 1677803510,
        address: addressB,
        to_address: null,
        amount: '2000000000000000000000',
      });

      const response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/tokens/pepe/holders`,
      });
      expect(response.statusCode).toBe(200);
      const json = response.json();
      expect(json.total).toBe(2);
      expect(json.results).toStrictEqual([
        {
          address: addressA,
          overall_balance: '10000.000000000000000000',
        },
        {
          address: addressB,
          overall_balance: '2000.000000000000000000',
        },
      ]);
    });

    test('shows empty list on token with no holders', async () => {
      const inscriptionNumbers = incrementing(0);
      const blockHeights = incrementing(767430);
      const addressA = 'bc1p3cyx5e2hgh53w7kpxcvm8s4kkega9gv5wfw7c4qxsvxl0u8x834qf0u2td';

      const number = inscriptionNumbers.next().value;
      const transferHash = randomHash();
      const blockHash = randomHash();
      await brc20TokenDeploy(brc20Db.sql, {
        ticker: 'pepe',
        display_ticker: 'pepe',
        inscription_id: `${transferHash}i0`,
        inscription_number: number.toString(),
        block_height: blockHeights.next().value.toString(),
        block_hash: blockHash,
        tx_id: transferHash,
        tx_index: 0,
        address: addressA,
        max: '21000000000000000000000000',
        limit: '21000000000000000000000000',
        decimals: 18,
        self_mint: false,
        minted_supply: '0',
        tx_count: 1,
        timestamp: 1677803510,
        operation: 'deploy',
        ordinal_number: '20000',
        output: `${transferHash}:0`,
        offset: '0',
        to_address: null,
        amount: '0',
      });
      const response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/tokens/pepe/holders`,
      });
      expect(response.statusCode).toBe(200);
      const json = response.json();
      expect(json.total).toBe(0);
      expect(json.results).toStrictEqual([]);
    });

    test('shows 404 on token not found', async () => {
      const response = await fastify.inject({
        method: 'GET',
        url: `/ordinals/brc-20/tokens/pepe/holders`,
      });
      expect(response.statusCode).toBe(404);
    });
  });
});
