/* eslint-disable react-hooks/rules-of-hooks */
import 'dotenv/config';
import { test as base, expect, Page, APIRequestContext, APIResponse } from '@playwright/test';

export type TenantCredentials = {
  email: string;
  password: string;
};

export type TenantAdminFixtures = {
  tenantAdminCredentials: TenantCredentials | null;
  signInTenantAdmin: () => Promise<void>;
};

export type MailboxEntry = {
  id: string;
  subject: string | null;
  to: Array<{ email: string; name: string | null }>;
  from: Array<{ email: string; name: string | null }>;
  html: string | null;
  text: string | null;
  sent_at: string;
};

export type CouponSeedDefinition = {
  code: string;
  type: 'percentage' | 'flat' | 'flat_per_seat';
  amount: number;
  currency?: string | null;
  description?: string;
  enabled_for_checkout?: boolean;
  usage_limit?: number | null;
  per_customer_limit?: number | null;
  starts_at?: string | null;
  ends_at?: string | null;
  packages?: number[];
};

export type TestingApiFixtures = {
  clearTestMailbox: () => Promise<void>;
  getTestMailbox: () => Promise<MailboxEntry[]>;
  seedTestCoupons: (definitions?: CouponSeedDefinition[]) => Promise<Array<{ id: number; code: string }>>;
  getLatestCheckoutSession: (filters?: { email?: string; tenantId?: number; status?: string }) => Promise<CheckoutSessionSummary | null>;
  simulatePaddleCompletion: (sessionId: string, overrides?: Partial<PaddleSimulationOverrides>) => Promise<void>;
  fetchJoinToken: (params: { eventId?: number; slug?: string; ensureActive?: boolean }) => Promise<JoinTokenPayload>;
};

export type CheckoutSessionSummary = {
  id: string;
  status: string;
  provider: string | null;
  tenant_id: number | null;
  package_id: number | null;
  user_email: string | null;
  coupon_id: number | null;
  amount_subtotal: string | null;
  amount_total: string | null;
  created_at: string | null;
};

export type PaddleSimulationOverrides = {
  event_type: string;
  transaction_id?: string;
  status?: string;
  checkout_id?: string;
  metadata?: Record<string, unknown>;
};

export type JoinTokenPayload = {
  event_id: number;
  token_id: number;
  token: string;
  join_url: string;
  qr_svg: string;
  expires_at: string | null;
  usage_count: number;
  usage_limit: number | null;
};

const tenantAdminEmail = process.env.E2E_TENANT_EMAIL ?? null;
const tenantAdminPassword = process.env.E2E_TENANT_PASSWORD ?? null;

export const test = base.extend<TenantAdminFixtures & TestingApiFixtures>({
  tenantAdminCredentials: async ({}, use) => {
    if (!tenantAdminEmail || !tenantAdminPassword) {
      await use(null);
      return;
    }

    await use({
      email: tenantAdminEmail,
      password: tenantAdminPassword,
    });
  },

  signInTenantAdmin: async ({ page, tenantAdminCredentials }, use) => {
    if (!tenantAdminCredentials) {
      await use(async () => {
        throw new Error('Tenant admin credentials missing. Provide E2E_TENANT_EMAIL and E2E_TENANT_PASSWORD.');
      });
      return;
    }

    await use(async () => {
      await performTenantSignIn(page, tenantAdminCredentials);
    });
  },

  clearTestMailbox: async ({ request }, use) => {
    await use(async () => {
      await expectApiSuccess(request.delete('/api/_testing/mailbox'));
    });
  },

  getTestMailbox: async ({ request }, use) => {
    await use(async () => {
      const response = await expectApiSuccess(request.get('/api/_testing/mailbox'));
      const json = await response.json();

      return Array.isArray(json.data) ? (json.data as MailboxEntry[]) : [];
    });
  },

  seedTestCoupons: async ({ request }, use) => {
    await use(async (definitions?: CouponSeedDefinition[]) => {
      const response = await expectApiSuccess(
        request.post('/api/_testing/coupons/seed', {
          data: definitions && definitions.length > 0 ? { coupons: definitions } : undefined,
        })
      );

      const json = await response.json();

      return Array.isArray(json.data) ? json.data : [];
    });
  },

  getLatestCheckoutSession: async ({ request }, use) => {
    await use(async (filters?: { email?: string; tenantId?: number; status?: string }) => {
      const response = await request.get('/api/_testing/checkout/sessions/latest', {
        params: {
          email: filters?.email,
          tenant_id: filters?.tenantId,
          status: filters?.status,
        },
      });

      if (response.status() === 404) {
        return null;
      }

      await expectApiSuccess(Promise.resolve(response));
      const json = await response.json();

      return json.data as CheckoutSessionSummary;
    });
  },

  simulatePaddleCompletion: async ({ request }, use) => {
    await use(async (sessionId: string, overrides?: Partial<PaddleSimulationOverrides>) => {
      await expectApiSuccess(
        request.post(`/api/_testing/checkout/sessions/${sessionId}/simulate-paddle`, {
          data: overrides,
        })
      );
    });
  },

  fetchJoinToken: async ({ request }, use) => {
    await use(async ({ eventId, slug, ensureActive = true }: { eventId?: number; slug?: string; ensureActive?: boolean }) => {
      const response = await expectApiSuccess(
        request.get('/api/_testing/events/join-token', {
          params: {
            event_id: eventId,
            slug,
            ensure_active: ensureActive,
          },
        })
      );

      const json = await response.json();

      return json.data as JoinTokenPayload;
    });
  },
});

export const expectFixture = expect;

async function performTenantSignIn(page: Page, credentials: TenantCredentials) {
  const token = await exchangeToken(page.request, credentials);

  await page.addInitScript(({ stored }) => {
    localStorage.setItem('tenant_admin.token.v1', JSON.stringify(stored));
    sessionStorage.setItem('tenant_admin.token.session.v1', JSON.stringify(stored));
  }, { stored: token });

  await page.goto('/event-admin');
  await page.waitForLoadState('domcontentloaded');
  await dismissConsentBanner(page);
}

type StoredTokenPayload = {
  accessToken: string;
  abilities: string[];
  issuedAt: number;
};

async function exchangeToken(request: APIRequestContext, credentials: TenantCredentials): Promise<StoredTokenPayload> {
  const response = await request.post('/api/v1/tenant-auth/login', {
    data: {
      login: credentials.email,
      password: credentials.password,
    },
  });

  if (!response.ok()) {
    throw new Error(`Tenant PAT login failed: ${response.status()} ${await response.text()}`);
  }

  const body = await response.json();

  return {
    accessToken: body.token,
    abilities: Array.isArray(body.abilities) ? body.abilities : [],
    issuedAt: Date.now(),
  };
}

async function expectApiSuccess(responsePromise: Promise<APIResponse>): Promise<APIResponse> {
  const response = await responsePromise;

  if (!response.ok()) {
    throw new Error(`Test API request failed: ${response.status()} ${await response.text()}`);
  }

  return response;
}

export async function dismissConsentBanner(page: Page): Promise<void> {
  const acceptButton = page.getByRole('button', {
    name: /consent\.banner\.accept|consent\.modal\.accept_all|Alle akzeptieren|Akzeptieren|Accept all|Accept/i,
  }).first();

  try {
    if (await acceptButton.isVisible({ timeout: 2000 })) {
      await acceptButton.click();
    }
  } catch {
    // Ignore missing banner or timing issues.
  }
}
