import { describe, it, expect, vi, beforeEach } from 'vitest';
import { WebhookHandler } from './webhook-handler.js';
import type { TelegramUpdate, TelegramMessage, PreCheckoutQuery, SuccessfulPayment } from '../../types/telegram.js';
import type { KIETaskResponse } from '../../types/api.js';

vi.mock('../../utils/index.js', () => ({
  createModuleLogger: () => ({
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
  }),
}));

const BOT_TOKEN = 'bot-token-123';
const BOT_TOKEN_2 = 'bot-token-456';

function makeTelegramMessage(overrides: Partial<TelegramMessage> = {}): TelegramMessage {
  return {
    message_id: 1,
    chat: { id: 100, type: 'private' },
    date: Math.floor(Date.now() / 1000),
    text: 'Hello',
    from: { id: 42, is_bot: false, first_name: 'Test' },
    ...overrides,
  };
}

function makePreCheckoutQuery(overrides: Partial<PreCheckoutQuery> = {}): PreCheckoutQuery {
  return {
    id: 'pchk-1',
    from: { id: 42, is_bot: false, first_name: 'Test' },
    currency: 'XTR',
    total_amount: 100,
    invoice_payload: 'image-abc',
    ...overrides,
  };
}

function makeSuccessfulPayment(overrides: Partial<SuccessfulPayment> = {}): SuccessfulPayment {
  return {
    currency: 'XTR',
    total_amount: 100,
    invoice_payload: 'image-abc',
    telegram_payment_charge_id: 'charge-1',
    provider_payment_charge_id: 'provider-1',
    ...overrides,
  };
}

describe('WebhookHandler', () => {
  let handler: WebhookHandler;

  beforeEach(() => {
    handler = new WebhookHandler(new Map([[BOT_TOKEN, 'char-1']]));
  });

  describe('constructor', () => {
    it('accepts a Map of tokens', () => {
      const h = new WebhookHandler(new Map([['tok1', 'c1'], ['tok2', 'c2']]));
      expect(h.validateToken('tok1')).toBe(true);
      expect(h.validateToken('tok2')).toBe(true);
    });

    it('accepts a Set of tokens', () => {
      const h = new WebhookHandler(new Set(['tok1', 'tok2']));
      expect(h.validateToken('tok1')).toBe(true);
    });

    it('accepts an array of tokens', () => {
      const h = new WebhookHandler(['tok1', 'tok2']);
      expect(h.validateToken('tok1')).toBe(true);
    });
  });

  describe('validateToken', () => {
    it('returns true for registered token', () => {
      expect(handler.validateToken(BOT_TOKEN)).toBe(true);
    });

    it('returns false for unregistered token', () => {
      expect(handler.validateToken('unknown-token')).toBe(false);
    });
  });

  describe('registerToken / unregisterToken', () => {
    it('registers a new token', () => {
      expect(handler.validateToken(BOT_TOKEN_2)).toBe(false);
      handler.registerToken(BOT_TOKEN_2);
      expect(handler.validateToken(BOT_TOKEN_2)).toBe(true);
    });

    it('unregisters a token and removes its handlers', () => {
      const msgHandler = vi.fn();
      handler.onMessage(BOT_TOKEN, msgHandler);
      handler.unregisterToken(BOT_TOKEN);

      expect(handler.validateToken(BOT_TOKEN)).toBe(false);
    });

    it('unregistering a non-existent token does not throw', () => {
      expect(() => handler.unregisterToken('nonexistent')).not.toThrow();
    });
  });

  describe('handleTelegramWebhook', () => {
    it('rejects missing bot token', async () => {
      const update: TelegramUpdate = { update_id: 1, message: makeTelegramMessage() };
      const result = await handler.handleTelegramWebhook('', update);
      expect(result).toEqual({ ok: false, error: 'Missing bot token' });
    });

    it('rejects invalid bot token', async () => {
      const update: TelegramUpdate = { update_id: 1, message: makeTelegramMessage() };
      const result = await handler.handleTelegramWebhook('wrong-token', update);
      expect(result).toEqual({ ok: false, error: 'Invalid bot token' });
    });

    it('rejects invalid payload (missing update_id)', async () => {
      const result = await handler.handleTelegramWebhook(BOT_TOKEN, {} as TelegramUpdate);
      expect(result).toEqual({ ok: false, error: 'Invalid payload' });
    });

    it('rejects null payload', async () => {
      const result = await handler.handleTelegramWebhook(BOT_TOKEN, null as unknown as TelegramUpdate);
      expect(result).toEqual({ ok: false, error: 'Invalid payload' });
    });

    describe('message handling', () => {
      it('routes text messages to onMessage handler', async () => {
        const msgHandler = vi.fn();
        handler.onMessage(BOT_TOKEN, msgHandler);

        const message = makeTelegramMessage({ text: 'Hi there' });
        const update: TelegramUpdate = { update_id: 1, message };

        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: true });
        expect(msgHandler).toHaveBeenCalledWith(BOT_TOKEN, message);
      });

      it('succeeds even without a registered message handler', async () => {
        const update: TelegramUpdate = { update_id: 1, message: makeTelegramMessage() };
        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: true });
      });

      it('handles async message handlers', async () => {
        const msgHandler = vi.fn().mockResolvedValue(undefined);
        handler.onMessage(BOT_TOKEN, msgHandler);

        const update: TelegramUpdate = { update_id: 1, message: makeTelegramMessage() };
        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: true });
        expect(msgHandler).toHaveBeenCalledOnce();
      });
    });

    describe('pre_checkout_query handling', () => {
      it('routes pre_checkout_query to handler', async () => {
        const pchkHandler = vi.fn();
        handler.onPreCheckoutQuery(BOT_TOKEN, pchkHandler);

        const query = makePreCheckoutQuery();
        const update: TelegramUpdate = { update_id: 2, pre_checkout_query: query };

        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: true });
        expect(pchkHandler).toHaveBeenCalledWith(BOT_TOKEN, query);
      });

      it('succeeds without a registered pre_checkout handler', async () => {
        const update: TelegramUpdate = { update_id: 2, pre_checkout_query: makePreCheckoutQuery() };
        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: true });
      });
    });

    describe('successful_payment handling', () => {
      it('routes successful_payment to handler', async () => {
        const payHandler = vi.fn();
        handler.onSuccessfulPayment(BOT_TOKEN, payHandler);

        const payment = makeSuccessfulPayment();
        const message = makeTelegramMessage({ successful_payment: payment });
        const update: TelegramUpdate = { update_id: 3, message };

        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: true });
        expect(payHandler).toHaveBeenCalledWith(BOT_TOKEN, payment, message);
      });

      it('does NOT call onMessage when successful_payment is present', async () => {
        const msgHandler = vi.fn();
        const payHandler = vi.fn();
        handler.onMessage(BOT_TOKEN, msgHandler);
        handler.onSuccessfulPayment(BOT_TOKEN, payHandler);

        const payment = makeSuccessfulPayment();
        const message = makeTelegramMessage({ successful_payment: payment });
        const update: TelegramUpdate = { update_id: 3, message };

        await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(payHandler).toHaveBeenCalledOnce();
        expect(msgHandler).not.toHaveBeenCalled();
      });
    });

    describe('combined updates', () => {
      it('handles pre_checkout_query and message in same update', async () => {
        const msgHandler = vi.fn();
        const pchkHandler = vi.fn();
        handler.onMessage(BOT_TOKEN, msgHandler);
        handler.onPreCheckoutQuery(BOT_TOKEN, pchkHandler);

        const update: TelegramUpdate = {
          update_id: 4,
          pre_checkout_query: makePreCheckoutQuery(),
          message: makeTelegramMessage(),
        };

        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: true });
        expect(pchkHandler).toHaveBeenCalledOnce();
        expect(msgHandler).toHaveBeenCalledOnce();
      });
    });

    describe('error handling', () => {
      it('returns error when message handler throws', async () => {
        handler.onMessage(BOT_TOKEN, () => { throw new Error('handler failed'); });

        const update: TelegramUpdate = { update_id: 5, message: makeTelegramMessage() };
        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: false, error: 'handler failed' });
      });

      it('returns error when async handler rejects', async () => {
        handler.onMessage(BOT_TOKEN, async () => { throw new Error('async fail'); });

        const update: TelegramUpdate = { update_id: 6, message: makeTelegramMessage() };
        const result = await handler.handleTelegramWebhook(BOT_TOKEN, update);
        expect(result).toEqual({ ok: false, error: 'async fail' });
      });
    });
  });

  describe('handleKIEWebhook', () => {
    it('processes a completed task', async () => {
      const kieHandler = vi.fn();
      handler.onKIEComplete(kieHandler);

      const payload: KIETaskResponse = {
        taskId: 'task-1',
        status: 'completed',
        resultUrl: 'https://cdn.kie.ai/result.jpg',
      };

      const result = await handler.handleKIEWebhook(payload);
      expect(result).toEqual({ ok: true });
      expect(kieHandler).toHaveBeenCalledWith(payload);
    });

    it('processes a failed task', async () => {
      const kieHandler = vi.fn();
      handler.onKIEComplete(kieHandler);

      const payload: KIETaskResponse = {
        taskId: 'task-2',
        status: 'failed',
        error: 'Content policy violation',
      };

      const result = await handler.handleKIEWebhook(payload);
      expect(result).toEqual({ ok: true });
      expect(kieHandler).toHaveBeenCalledWith(payload);
    });

    it('calls multiple KIE handlers', async () => {
      const handler1 = vi.fn();
      const handler2 = vi.fn();
      handler.onKIEComplete(handler1);
      handler.onKIEComplete(handler2);

      const payload: KIETaskResponse = { taskId: 'task-3', status: 'processing' };
      await handler.handleKIEWebhook(payload);

      expect(handler1).toHaveBeenCalledOnce();
      expect(handler2).toHaveBeenCalledOnce();
    });

    it('rejects null payload', async () => {
      const result = await handler.handleKIEWebhook(null);
      expect(result).toEqual({ ok: false, error: 'Invalid payload' });
    });

    it('rejects non-object payload', async () => {
      const result = await handler.handleKIEWebhook('not-an-object');
      expect(result).toEqual({ ok: false, error: 'Invalid payload' });
    });

    it('rejects payload without taskId', async () => {
      const result = await handler.handleKIEWebhook({ status: 'completed' });
      expect(result).toEqual({ ok: false, error: 'Missing taskId' });
    });

    it('rejects payload without status', async () => {
      const result = await handler.handleKIEWebhook({ taskId: 'task-1' });
      expect(result).toEqual({ ok: false, error: 'Missing status' });
    });

    it('rejects payload with invalid status', async () => {
      const result = await handler.handleKIEWebhook({ taskId: 'task-1', status: 'unknown' });
      expect(result).toEqual({ ok: false, error: 'Invalid status' });
    });

    it('returns error when KIE handler throws', async () => {
      handler.onKIEComplete(() => { throw new Error('kie handler failed'); });

      const payload: KIETaskResponse = { taskId: 'task-4', status: 'completed' };
      const result = await handler.handleKIEWebhook(payload);
      expect(result).toEqual({ ok: false, error: 'kie handler failed' });
    });

    it('succeeds without any registered KIE handlers', async () => {
      const payload: KIETaskResponse = { taskId: 'task-5', status: 'completed' };
      const result = await handler.handleKIEWebhook(payload);
      expect(result).toEqual({ ok: true });
    });

    it('strips unknown fields from payload', async () => {
      const kieHandler = vi.fn();
      handler.onKIEComplete(kieHandler);

      const payload = { taskId: 'task-6', status: 'completed', resultUrl: 'https://example.com/img.jpg', extraField: 'ignored' };
      await handler.handleKIEWebhook(payload);

      const received = kieHandler.mock.calls[0][0] as KIETaskResponse;
      expect(received.taskId).toBe('task-6');
      expect(received.status).toBe('completed');
      expect(received.resultUrl).toBe('https://example.com/img.jpg');
      expect((received as unknown as Record<string, unknown>)['extraField']).toBeUndefined();
    });
  });
});
