import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider } from 'styled-components';
import BillingSection from './BillingSection';

const mockGet = jest.fn();
const mockPost = jest.fn();
const assignMock = jest.fn();
const mockTrackEvent = jest.fn();
const mockPurchasePlan = jest.fn();
const mockRestorePurchases = jest.fn();
const mockIsNativePlatform = jest.fn(() => false);

jest.mock('../../../axiosApi', () => ({
  axiosInstance: {
    get: (...args: unknown[]) => mockGet(...args),
    post: (...args: unknown[]) => mockPost(...args),
  },
}));

jest.mock('../../utils/analytics', () => ({
  AnalyticsEvents: {
    BILLING_PORTAL_OPENED: 'Billing Portal Opened',
    CHECKOUT_STARTED: 'Checkout Started',
    SUBSCRIPTION_UPGRADE_STARTED: 'Subscription Upgrade Started',
    PLAN_CHANGE_STARTED: 'Plan Change Started',
    SUBSCRIPTION_CANCEL_STARTED: 'Subscription Cancel Started',
  },
  trackEvent: (...args: unknown[]) => mockTrackEvent(...args),
}));

jest.mock('../../platform/nativePlatform', () => ({
  isNativePlatform: () => mockIsNativePlatform(),
}));

jest.mock('../../utils/revenueCat', () => ({
  purchasePlan: (...args: unknown[]) => mockPurchasePlan(...args),
  restorePurchases: (...args: unknown[]) => mockRestorePurchases(...args),
  isPurchaseCancelledError: (error: { userCancelled?: boolean }) =>
    Boolean(error?.userCancelled),
}));

const theme = {
  main: '#4a90e2',
  darkMode: true,
  colors: {
    text: '#ffffff',
    cardBackground: 'rgba(0,0,0,0.4)',
    cardBorder: 'rgba(255,255,255,0.1)',
  },
};

const paidInvoice = {
  id: 1,
  provider: 'stripe',
  status: 'paid',
  currency: 'usd',
  amount_due: 1000,
  amount_paid: 1000,
  period_start: '2026-07-01T00:00:00Z',
  period_end: '2026-08-01T00:00:00Z',
  stripe_invoice_id: 'in_test',
  stripe_checkout_session_id: 'cs_test',
  stripe_subscription_id: 'sub_test',
  hosted_invoice_url: 'https://invoice.stripe.com/i/test',
  description: 'Chat Subscription',
  created: '2026-07-01T12:00:00Z',
  last_modified: '2026-07-01T12:00:00Z',
};

const foundersPlan = {
  slug: 'founders',
  name: 'Founders',
  description: '',
  price_cents: 1000,
  currency: 'usd',
  interval: 'month',
  is_public: true,
  is_selectable: true,
  features: {
    text_generation: true,
    image_generation: true,
    all_future_features: true,
  },
  prompt_quota_per_window: 300,
  prompt_window_hours: 6,
  monthly_token_quota: null,
  sort_order: 10,
};

const foundersSubscription = {
  plan: foundersPlan,
  status: 'active',
  source: 'stripe',
  needs_checkout: false,
  stripe_subscription_id: 'sub_test',
  cancel_at_period_end: false,
  current_period_end: '2026-08-01T00:00:00Z',
  usage: {
    prompts_in_window: 2,
    prompt_quota: 300,
    prompts_remaining: 298,
    window_hours: 6,
    tokens_in_period: null,
    tokens_out_period: null,
    tokens_total_period: null,
    turns_missing_token_usage: 0,
    monthly_token_quota: null,
    tokens_remaining: null,
    period_start: null,
    period_end: null,
  },
};

const complimentarySubscription = {
  ...foundersSubscription,
  source: 'backer',
  stripe_subscription_id: '',
};

const emptySubscription = {
  plan: null,
  status: 'none',
  source: 'none',
  needs_checkout: true,
  stripe_subscription_id: '',
  cancel_at_period_end: false,
  current_period_end: null,
  usage: {
    prompts_in_window: 0,
    prompt_quota: null,
    prompts_remaining: null,
    window_hours: 6,
    tokens_in_period: null,
    tokens_out_period: null,
    tokens_total_period: null,
    turns_missing_token_usage: 0,
    monthly_token_quota: null,
    tokens_remaining: null,
    period_start: null,
    period_end: null,
  },
};

type SubscriptionMock = typeof emptySubscription | typeof foundersSubscription;

const mockFinanceGets = ({
  invoices = [] as unknown[],
  payments = [] as unknown[],
  subscription = emptySubscription as SubscriptionMock,
  plans = [foundersPlan] as unknown[],
}: {
  invoices?: unknown[];
  payments?: unknown[];
  subscription?: SubscriptionMock;
  plans?: unknown[];
} = {}) => {
  mockGet.mockImplementation((url: string) => {
    if (url === '/monetization/invoices/') {
      return Promise.resolve({ data: invoices });
    }
    if (url === '/monetization/payments/') {
      return Promise.resolve({ data: payments });
    }
    if (url === '/monetization/subscription/') {
      return Promise.resolve({ data: subscription });
    }
    if (url === '/monetization/plans/') {
      return Promise.resolve({ data: plans });
    }
    return Promise.reject(new Error(`unexpected GET ${url}`));
  });
};

const renderBilling = () =>
  render(
    <ThemeProvider theme={theme}>
      <BillingSection />
    </ThemeProvider>
  );

describe('BillingSection', () => {
  const originalLocation = window.location;

  beforeEach(() => {
    mockGet.mockReset();
    mockPost.mockReset();
    assignMock.mockReset();
    mockTrackEvent.mockReset();
    mockPurchasePlan.mockReset();
    mockRestorePurchases.mockReset();
    mockIsNativePlatform.mockReturnValue(false);
    Object.defineProperty(window, 'location', {
      configurable: true,
      value: {
        ...originalLocation,
        assign: assignMock,
        origin: 'http://localhost',
      },
    });
  });

  afterEach(() => {
    Object.defineProperty(window, 'location', {
      configurable: true,
      value: originalLocation,
    });
  });

  it('renders plan summary and paid subscription management CTAs', async () => {
    mockFinanceGets({
      invoices: [paidInvoice],
      subscription: foundersSubscription,
    });

    renderBilling();

    expect(await screen.findByRole('button', { name: /^Upgrade$/i })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Change plan/i })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /^Cancel$/i })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Manage subscription/i })).toBeInTheDocument();
    expect(screen.getByText('Founders')).toBeInTheDocument();
    expect(screen.getByText(/298 \/ 300/)).toBeInTheDocument();
    expect(screen.getByText(/in — · out —/)).toBeInTheDocument();
    expect(screen.getByText('View')).toHaveAttribute(
      'href',
      'https://invoice.stripe.com/i/test'
    );
  });

  it('opens Stripe portal for change plan when no alternate public plans', async () => {
    mockFinanceGets({
      invoices: [paidInvoice],
      subscription: foundersSubscription,
      plans: [foundersPlan],
    });
    mockPost.mockResolvedValue({
      data: { portal_url: 'https://billing.stripe.com/p/session/test' },
    });

    const user = userEvent.setup();
    renderBilling();

    await screen.findByRole('button', { name: /Change plan/i });
    await user.click(screen.getByRole('button', { name: /Change plan/i }));

    await waitFor(() => {
      expect(mockPost).toHaveBeenCalledWith('/monetization/portal/', {
        return_url: 'http://localhost/account/',
      });
    });
    expect(mockTrackEvent).toHaveBeenCalledWith('Plan Change Started', {
      source: 'account_billing',
    });
    expect(mockTrackEvent).toHaveBeenCalledWith('Billing Portal Opened', {
      intent: 'change',
    });
    expect(assignMock).toHaveBeenCalledWith(
      'https://billing.stripe.com/p/session/test'
    );
  });

  it('confirms cancel then opens portal', async () => {
    mockFinanceGets({
      invoices: [paidInvoice],
      subscription: foundersSubscription,
    });
    mockPost.mockResolvedValue({
      data: { portal_url: 'https://billing.stripe.com/p/session/cancel' },
    });

    const user = userEvent.setup();
    renderBilling();

    await screen.findByRole('button', { name: /^Cancel$/i });
    await user.click(screen.getByRole('button', { name: /^Cancel$/i }));
    expect(await screen.findByTestId('cancel-confirm-modal')).toBeInTheDocument();
    await user.click(screen.getByRole('button', { name: /Continue to cancel/i }));

    await waitFor(() => {
      expect(mockTrackEvent).toHaveBeenCalledWith('Subscription Cancel Started', {
        source: 'portal',
      });
    });
    expect(assignMock).toHaveBeenCalledWith(
      'https://billing.stripe.com/p/session/cancel'
    );
  });

  it('shows complimentary messaging without cancel', async () => {
    mockFinanceGets({
      invoices: [],
      subscription: complimentarySubscription,
    });

    renderBilling();

    expect(
      await screen.findByTestId('complimentary-billing-message')
    ).toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /^Cancel$/i })).not.toBeInTheDocument();
    expect(screen.queryByRole('button', { name: /^Upgrade$/i })).not.toBeInTheDocument();
  });

  it('shows empty unpaid state and starts checkout', async () => {
    mockFinanceGets();
    mockPost.mockResolvedValue({
      data: { checkout_url: 'https://checkout.stripe.com/c/pay/cs_test' },
    });

    const user = userEvent.setup();
    renderBilling();

    expect(
      await screen.findByText(/No active subscription yet/i)
    ).toBeInTheDocument();
    expect(screen.getByText(/No invoices or payments yet/i)).toBeInTheDocument();

    await user.click(screen.getByRole('button', { name: /Complete payment/i }));

    await waitFor(() => {
      expect(mockPost).toHaveBeenCalledWith(
        '/monetization/checkout/',
        expect.objectContaining({
          success_url: expect.stringContaining('/billing/success'),
          cancel_url: expect.stringContaining('/billing/cancel'),
        })
      );
    });
    expect(assignMock).toHaveBeenCalledWith(
      'https://checkout.stripe.com/c/pay/cs_test'
    );
  });

  it('shows visible error when finance list fails', async () => {
    mockGet.mockRejectedValue({
      response: { data: { detail: 'Finance unavailable' } },
    });

    renderBilling();

    expect(await screen.findAllByRole('alert')).not.toHaveLength(0);
    expect(screen.getAllByText('Finance unavailable').length).toBeGreaterThan(0);
  });

  it('shows visible error when portal open fails', async () => {
    mockFinanceGets({
      invoices: [paidInvoice],
      subscription: foundersSubscription,
    });
    mockPost.mockRejectedValue({
      response: { data: { detail: 'No Stripe customer found' } },
    });

    const user = userEvent.setup();
    renderBilling();

    await screen.findByRole('button', { name: /Manage subscription/i });
    await user.click(screen.getByRole('button', { name: /Manage subscription/i }));

    expect(await screen.findByText('No Stripe customer found')).toBeInTheDocument();
    expect(assignMock).not.toHaveBeenCalled();
  });

  it('on native uses RevenueCat purchase and shows Restore', async () => {
    mockIsNativePlatform.mockReturnValue(true);
    mockFinanceGets();
    mockPurchasePlan.mockResolvedValue(undefined);

    const user = userEvent.setup();
    renderBilling();

    expect(await screen.findByTestId('restore-purchases')).toBeInTheDocument();
    await user.click(screen.getByRole('button', { name: /Complete payment/i }));

    await waitFor(() => {
      expect(mockPurchasePlan).toHaveBeenCalled();
    });
    expect(mockPost).not.toHaveBeenCalledWith(
      '/monetization/checkout/',
      expect.anything()
    );
  });

  it('shows provider badge for RevenueCat invoices', async () => {
    mockFinanceGets({
      invoices: [
        {
          ...paidInvoice,
          provider: 'revenuecat',
          stripe_subscription_id: null,
          revenuecat_store: 'PLAY_STORE',
          hosted_invoice_url: '',
          description: 'Store IAP (PLAY_STORE) — founders — INITIAL_PURCHASE',
        },
      ],
      subscription: {
        ...foundersSubscription,
        source: 'revenuecat',
        stripe_subscription_id: '',
      },
    });

    renderBilling();

    expect(await screen.findByText('Play Store')).toBeInTheDocument();
    expect(screen.getByText(/Store IAP/)).toBeInTheDocument();
  });
});
