import AsyncStorage from '@react-native-async-storage/async-storage';
import { mockBackendService } from '../../services/backend/mockBackendService';

jest.mock('@react-native-async-storage/async-storage', () => ({
  getItem: jest.fn(),
  setItem: jest.fn(),
  removeItem: jest.fn(),
}));

const asyncStorageMemory: Record<string, string> = {};

const mockedAsyncStorage = AsyncStorage as jest.Mocked<typeof AsyncStorage>;

const runScan = async (userId: string, idempotencyKey: string) => {
  const settledPromise = mockBackendService.scanPlant({
    userId,
    idempotencyKey,
    imageUri: `data:image/jpeg;base64,${idempotencyKey}`,
    language: 'en',
  }).then(
    value => ({ ok: true as const, value }),
    error => ({ ok: false as const, error }),
  );
  await Promise.resolve();
  await jest.runAllTimersAsync();
  const settled = await settledPromise;
  if (!settled.ok) throw settled.error;
  return settled.value;
};

describe('mockBackendService billing simulation', () => {
  beforeEach(() => {
    jest.useFakeTimers();
    Object.keys(asyncStorageMemory).forEach((key) => {
      delete asyncStorageMemory[key];
    });

    mockedAsyncStorage.getItem.mockImplementation(async (key: string) => {
      return key in asyncStorageMemory ? asyncStorageMemory[key] : null;
    });

    mockedAsyncStorage.setItem.mockImplementation(async (key: string, value: string) => {
      asyncStorageMemory[key] = value;
    });

    mockedAsyncStorage.removeItem.mockImplementation(async (key: string) => {
      delete asyncStorageMemory[key];
    });
  });

  afterEach(() => {
    jest.useRealTimers();
    jest.clearAllMocks();
  });

  it('keeps simulatePurchase idempotent for same idempotency key', async () => {
    const userId = 'test-user-idempotency';
    const idempotencyKey = 'purchase-1';

    const first = await mockBackendService.simulatePurchase({
      userId,
      idempotencyKey,
      productId: 'topup_small',
    });
    const second = await mockBackendService.simulatePurchase({
      userId,
      idempotencyKey,
      productId: 'topup_small',
    });

    expect(first.billing.credits.topupBalance).toBe(25);
    expect(second.billing.credits.topupBalance).toBe(25);
  });

  it('consumes plan credits before topup credits', async () => {
    const userId = 'test-user-credit-order';
    await mockBackendService.simulatePurchase({
      userId,
      idempotencyKey: 'topup-order-1',
      productId: 'topup_small',
    });

    let lastScan = await runScan(userId, 'scan-order-0');
    expect(lastScan.billing.credits.usedThisCycle).toBe(1);
    expect(lastScan.billing.credits.topupBalance).toBe(25);

    for (let i = 1; i <= 15; i += 1) {
      lastScan = await runScan(userId, `scan-order-${i}`);
    }

    expect(lastScan.billing.credits.usedThisCycle).toBe(15);
    expect(lastScan.billing.credits.topupBalance).toBe(24);
  });

  it('can deplete all available credits via webhook simulation', async () => {
    const userId = 'test-user-deplete-credits';
    await mockBackendService.simulatePurchase({
      userId,
      idempotencyKey: 'topup-deplete-1',
      productId: 'topup_small',
    });

    const response = await mockBackendService.simulateWebhook({
      userId,
      idempotencyKey: 'webhook-deplete-1',
      event: 'credits_depleted',
    });

    expect(response.billing.credits.available).toBe(0);
    expect(response.billing.credits.topupBalance).toBe(0);
    expect(response.billing.credits.usedThisCycle).toBe(response.billing.credits.monthlyAllowance);
  });

  it('does not double-charge scan when idempotency key is reused', async () => {
    const userId = 'test-user-scan-idempotency';
    const first = await runScan(userId, 'scan-abc');
    const second = await runScan(userId, 'scan-abc');

    expect(first.creditsCharged).toBe(1);
    expect(second.creditsCharged).toBe(1);
    expect(second.billing.credits.available).toBe(first.billing.credits.available);
  });

  it('enforces free monthly credit limit', async () => {
    const userId = 'test-user-credit-limit';
    let successfulScans = 0;
    let errorCode: string | null = null;

    for (let i = 0; i < 30; i += 1) {
      try {
        await runScan(userId, `scan-${i}`);
        successfulScans += 1;
      } catch (error) {
        errorCode = (error as { code?: string }).code || null;
        break;
      }
    }

    expect(errorCode).toBe('INSUFFICIENT_CREDITS');
    expect(successfulScans).toBeGreaterThanOrEqual(7);
    expect(successfulScans).toBeLessThanOrEqual(15);
  });

  it('syncs pro entitlement from RevenueCat customer info', async () => {
    const response = await mockBackendService.syncRevenueCatState({
      userId: 'test-user-rc-pro',
      customerInfo: {
        entitlements: {
          active: {
            pro: {
              productIdentifier: 'monthly_pro',
              expirationDate: '2026-04-30T00:00:00.000Z',
            },
          },
        },
        nonSubscriptions: {},
      },
    });

    expect(response.billing.entitlement.plan).toBe('pro');
    expect(response.billing.entitlement.status).toBe('active');
    expect(response.billing.entitlement.renewsAt).toBe('2026-04-30T00:00:00.000Z');
  });

  it('credits RevenueCat top-up transactions only once', async () => {
    const userId = 'test-user-rc-topup';
    await mockBackendService.syncRevenueCatState({
      userId,
      customerInfo: {
        entitlements: { active: {} },
        nonSubscriptions: {
          topup_small: [
            {
              productIdentifier: 'topup_small',
              transactionIdentifier: 'rc-topup-1',
            },
          ],
        },
      },
    });

    const second = await mockBackendService.syncRevenueCatState({
      userId,
      customerInfo: {
        entitlements: { active: {} },
        nonSubscriptions: {
          topup_small: [
            {
              productIdentifier: 'topup_small',
              transactionIdentifier: 'rc-topup-1',
            },
          ],
        },
      },
    });

    expect(second.billing.credits.topupBalance).toBe(25);
  });

  it('ignores malformed pro entitlements coming from top-up customer info', async () => {
    const response = await mockBackendService.syncRevenueCatState({
      userId: 'test-user-rc-topup-misconfigured-entitlement',
      source: 'topup_purchase',
      customerInfo: {
        entitlements: {
          active: {
            pro: {
              productIdentifier: 'topup_small',
              expirationDate: '2026-04-30T00:00:00.000Z',
            },
          },
        },
        nonSubscriptions: {
          topup_small: [
            {
              productIdentifier: 'topup_small',
              transactionIdentifier: 'rc-topup-malformed-1',
            },
          ],
        },
      },
    });

    expect(response.billing.entitlement.plan).toBe('free');
    expect(response.billing.entitlement.status).toBe('inactive');
    expect(response.billing.credits.topupBalance).toBe(25);
    expect(response.billing.credits.available).toBe(40);
  });

  it('does not downgrade an existing pro user during a top-up sync', async () => {
    const userId = 'test-user-rc-pro-topup';

    await mockBackendService.syncRevenueCatState({
      userId,
      source: 'subscription_purchase',
      customerInfo: {
        entitlements: {
          active: {
            pro: {
              productIdentifier: 'monthly_pro',
              expirationDate: '2026-04-30T00:00:00.000Z',
            },
          },
        },
        nonSubscriptions: {},
      },
    });

    const response = await mockBackendService.syncRevenueCatState({
      userId,
      source: 'topup_purchase',
      customerInfo: {
        entitlements: {
          active: {
            pro: {
              productIdentifier: 'topup_small',
              expirationDate: '2026-04-30T00:00:00.000Z',
            },
          },
        },
        nonSubscriptions: {
          topup_small: [
            {
              productIdentifier: 'topup_small',
              transactionIdentifier: 'rc-topup-pro-1',
            },
          ],
        },
      },
    });

    expect(response.billing.entitlement.plan).toBe('pro');
    expect(response.billing.credits.available).toBe(275);
    expect(response.billing.credits.topupBalance).toBe(25);
  });
});
