import { afterEach, beforeAll, describe, expect, it } from 'vitest';
import { createHmac } from 'crypto';
import type { FastifyInstance } from 'fastify';

const originalEnv = { ...process.env };

function setTestEnv(): void {
  process.env.NODE_ENV = 'development';
  process.env.PORT = '4003';
  process.env.DATABASE_URL = 'postgresql://postgres:postgres@127.0.0.1:5432/the_order_test';
  process.env.STORAGE_BUCKET = 'test-bucket';
  process.env.KMS_KEY_ID = 'test-kms-key';
  process.env.JWT_SECRET = 'test-jwt-secret-which-is-long-enough-1234567890';
}

function base64Url(value: string): string {
  return Buffer.from(value).toString('base64url');
}

function createJwt(payload: Record<string, unknown>): string {
  const header = base64Url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
  const body = base64Url(JSON.stringify(payload));
  const signature = createHmac('sha256', process.env.JWT_SECRET as string)
    .update(`${header}.${body}`)
    .digest('base64url');
  return `${header}.${body}.${signature}`;
}

let createServer: (typeof import('./index'))['createServer'];

beforeAll(async () => {
  setTestEnv();
  ({ createServer } = await import('./index'));
});

describe('Finance Service', () => {
  let server: FastifyInstance;

  afterEach(async () => {
    if (server) {
      await server.close();
    }
    process.env = { ...originalEnv, ...process.env };
  });

  it('returns service health', async () => {
    server = await createServer({ disableSwagger: true });
    await server.ready();

    const response = await server.inject({
      method: 'GET',
      url: '/health',
    });

    expect(response.statusCode).toBe(200);
    expect(response.json()).toMatchObject({
      service: 'finance',
    });
  });

  it('creates a basket-backed BTC deposit and exposes holdings plus rebalance plan', async () => {
    server = await createServer({ disableSwagger: true });
    await server.ready();

    const token = createJwt({
      id: 'client-1',
      roles: ['client', 'finance'],
    });

    const depositResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/btc-deposits',
      headers: {
        authorization: `Bearer ${token}`,
      },
      payload: {
        clientId: 'client-1',
        mandateName: 'Solace BTC jewelry box',
        chain138VaultAddress: '0x1111111111111111111111111111111111111111',
        allocations: [
          { symbol: 'cBTC', targetWeightBps: 4000 },
          { symbol: 'cUSDT', targetWeightBps: 3500 },
          { symbol: 'cXAUC', targetWeightBps: 2500 },
        ],
        expectedAmountSats: 250000000,
      },
    });

    expect(depositResponse.statusCode).toBe(201);
    const depositPayload = depositResponse.json() as {
      deposit: { id: string; basketId: string; status: string; confirmationsRequired: number; depositAddress: string };
      basket: { id: string };
      createdBasket: boolean;
    };

    expect(depositPayload.createdBasket).toBe(true);
    expect(depositPayload.deposit.status).toBe('instruction_created');
    expect(depositPayload.deposit.confirmationsRequired).toBe(6);
    expect(depositPayload.deposit.depositAddress.startsWith('bc1q')).toBe(true);
    expect(depositPayload.deposit.basketId).toBe(depositPayload.basket.id);

    const holdingsResponse = await server.inject({
      method: 'GET',
      url: `/api/v1/holdings?clientId=client-1&basketId=${depositPayload.basket.id}`,
      headers: {
        authorization: `Bearer ${token}`,
      },
    });

    expect(holdingsResponse.statusCode).toBe(200);
    const holdingsPayload = holdingsResponse.json() as {
      holdings: Array<{ symbol: string; allocationWeightBps: number; status: string }>;
    };
    expect(holdingsPayload.holdings).toHaveLength(3);
    expect(holdingsPayload.holdings[1]).toMatchObject({
      symbol: 'cUSDT',
      allocationWeightBps: 3500,
      status: 'pending_funding',
    });

    const rebalancesResponse = await server.inject({
      method: 'GET',
      url: `/api/v1/rebalances?basketId=${depositPayload.basket.id}`,
      headers: {
        authorization: `Bearer ${token}`,
      },
    });

    expect(rebalancesResponse.statusCode).toBe(200);
    const rebalancesPayload = rebalancesResponse.json() as {
      rebalances: Array<{ sourceSymbol: string; targetSymbols: string[]; status: string }>;
    };
    expect(rebalancesPayload.rebalances).toHaveLength(1);
    expect(rebalancesPayload.rebalances[0]).toMatchObject({
      sourceSymbol: 'cBTC',
      status: 'planned',
    });
    expect(rebalancesPayload.rebalances[0].targetSymbols).toEqual(['cUSDT', 'cXAUC']);
  });

  it('defaults bridge withdrawals to cBTC -> cWBTC', async () => {
    server = await createServer({ disableSwagger: true });
    await server.ready();

    const token = createJwt({
      id: 'client-2',
      roles: ['client'],
    });

    const basketResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/baskets',
      headers: {
        authorization: `Bearer ${token}`,
      },
      payload: {
        clientId: 'client-2',
        mandateName: 'Client 2 basket',
        chain138VaultAddress: '0x2222222222222222222222222222222222222222',
        allocations: [{ symbol: 'cBTC', targetWeightBps: 10000 }],
      },
    });

    const basketId = (basketResponse.json() as { basket: { id: string } }).basket.id;

    const withdrawalResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/withdrawals/bridge',
      headers: {
        authorization: `Bearer ${token}`,
      },
      payload: {
        clientId: 'client-2',
        basketId,
        destinationChainId: 1,
        destinationAddress: '0x3333333333333333333333333333333333333333',
        amount: '125000000',
      },
    });

    expect(withdrawalResponse.statusCode).toBe(201);
    expect(withdrawalResponse.json()).toMatchObject({
      withdrawal: {
        sourceSymbol: 'cBTC',
        destinationSymbol: 'cWBTC',
        destinationChainId: 1,
      },
    });
  });

  it('rejects cross-client access to another client basket', async () => {
    server = await createServer({ disableSwagger: true });
    await server.ready();

    const ownerToken = createJwt({
      id: 'client-owner',
      roles: ['client'],
    });
    const otherClientToken = createJwt({
      id: 'client-other',
      roles: ['client'],
    });

    const basketResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/baskets',
      headers: {
        authorization: `Bearer ${ownerToken}`,
      },
      payload: {
        clientId: 'client-owner',
        mandateName: 'Owner basket',
        chain138VaultAddress: '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
        allocations: [{ symbol: 'cBTC', targetWeightBps: 10000 }],
      },
    });

    const basketId = (basketResponse.json() as { basket: { id: string } }).basket.id;

    const holdingsResponse = await server.inject({
      method: 'GET',
      url: `/api/v1/holdings?basketId=${basketId}`,
      headers: {
        authorization: `Bearer ${otherClientToken}`,
      },
    });

    expect(holdingsResponse.statusCode).toBe(403);

    const withdrawalResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/withdrawals/bridge',
      headers: {
        authorization: `Bearer ${otherClientToken}`,
      },
      payload: {
        clientId: 'client-other',
        basketId,
        destinationChainId: 1,
        destinationAddress: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
        amount: '1000',
      },
    });

    expect(withdrawalResponse.statusCode).toBe(403);
  });

  it('rejects unknown basket ids and mismatched vault addresses on deposit creation', async () => {
    server = await createServer({ disableSwagger: true });
    await server.ready();

    const token = createJwt({
      id: 'client-3',
      roles: ['client'],
    });

    const unknownBasketResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/btc-deposits',
      headers: {
        authorization: `Bearer ${token}`,
      },
      payload: {
        clientId: 'client-3',
        basketId: '11111111-1111-4111-8111-111111111111',
        chain138VaultAddress: '0xcccccccccccccccccccccccccccccccccccccccc',
      },
    });

    expect(unknownBasketResponse.statusCode).toBe(404);

    const basketResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/baskets',
      headers: {
        authorization: `Bearer ${token}`,
      },
      payload: {
        clientId: 'client-3',
        mandateName: 'Vault match basket',
        chain138VaultAddress: '0xdddddddddddddddddddddddddddddddddddddddd',
        allocations: [{ symbol: 'cBTC', targetWeightBps: 10000 }],
      },
    });

    const basketId = (basketResponse.json() as { basket: { id: string } }).basket.id;

    const mismatchedVaultResponse = await server.inject({
      method: 'POST',
      url: '/api/v1/btc-deposits',
      headers: {
        authorization: `Bearer ${token}`,
      },
      payload: {
        clientId: 'client-3',
        basketId,
        chain138VaultAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
      },
    });

    expect(mismatchedVaultResponse.statusCode).toBe(400);
  });

  it('validates payment payloads on the versioned route', async () => {
    server = await createServer({ disableSwagger: true });
    await server.ready();

    const token = createJwt({
      id: 'finance-user',
      roles: ['finance'],
    });

    const response = await server.inject({
      method: 'POST',
      url: '/api/v1/payments',
      headers: {
        authorization: `Bearer ${token}`,
      },
      payload: {},
    });

    expect(response.statusCode).toBe(400);
  });
});
