import { test, expect, APIRequestContext } from '@playwright/test';
import {
  WORKER_URL,
  WORKER_URL_SEND_MAIL_DOMAIN,
  createTestAddress,
  deleteAddress,
  deleteAllMailpitMessages,
  requestSendAccess,
  onMailpitMessage,
} from '../../fixtures/test-helpers';

const ADMIN_PASSWORD = 'e2e-admin-pass';
const ADMIN_HEADERS = { 'x-admin-auth': ADMIN_PASSWORD };

const DEFAULT_ACCOUNT_SETTINGS = {
  blockList: [],
  sendBlockList: [],
  verifiedAddressList: [],
  fromBlockList: [],
  noLimitSendAddressList: [],
  emailRuleSettings: {},
  addressCreationSettings: {},
};

const DISABLED_LIMIT_CONFIG = {
  dailyEnabled: false,
  monthlyEnabled: false,
  dailyLimit: null as number | null,
  monthlyLimit: null as number | null,
};

async function saveLimitConfig(
  request: APIRequestContext,
  sendMailLimitConfig: Record<string, unknown>
) {
  return request.post(`${WORKER_URL}/admin/account_settings`, {
    headers: ADMIN_HEADERS,
    data: { ...DEFAULT_ACCOUNT_SETTINGS, sendMailLimitConfig },
  });
}

async function resetLimitConfig(request: APIRequestContext) {
  const res = await saveLimitConfig(request, DISABLED_LIMIT_CONFIG);
  expect(res.ok()).toBe(true);
}

async function sendOneMail(
  request: APIRequestContext,
  jwt: string,
  tag: string,
  opts: { expectDelivery?: boolean; lang?: string } = {}
) {
  const { expectDelivery = true, lang } = opts;
  const subject = `limit-${tag}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  const headers: Record<string, string> = { Authorization: `Bearer ${jwt}` };
  if (lang) headers['x-lang'] = lang;

  let listener: ReturnType<typeof onMailpitMessage> | undefined;
  if (expectDelivery) {
    listener = onMailpitMessage((m) => m.Subject === subject);
    await listener.ready;
  }
  const res = await request.post(`${WORKER_URL}/api/send_mail`, {
    headers,
    data: {
      from_name: 'Limit E2E',
      to_name: 'Recipient',
      to_mail: 'recipient@test.example.com',
      subject,
      content: `Limit test body ${tag}`,
      is_html: false,
    },
  });
  return { res, listener, subject };
}

async function probeLimitBaseline(
  request: APIRequestContext,
  jwt: string,
  config: {
    dailyEnabled: boolean;
    monthlyEnabled: boolean;
    dailyLimit: number | null;
    monthlyLimit: number | null;
  },
  subjectPrefix: string,
  maxProbeLimit: number = 50
): Promise<number> {
  for (let limit = 1; limit <= maxProbeLimit; limit++) {
    const save = await saveLimitConfig(request, {
      ...config,
      dailyLimit: config.dailyEnabled ? limit : null,
      monthlyLimit: config.monthlyEnabled ? limit : null,
    });
    expect(save.ok()).toBe(true);

    const probe = await request.post(`${WORKER_URL}/api/send_mail`, {
      headers: { Authorization: `Bearer ${jwt}` },
      data: {
        from_name: 'probe',
        to_name: '',
        to_mail: 'recipient@test.example.com',
        subject: `${subjectPrefix}-${limit}-${Date.now()}`,
        content: 'probe',
        is_html: false,
      },
    });
    if (probe.ok()) {
      return limit;
    }
  }
  throw new Error(`Failed to probe send mail limit baseline within ${maxProbeLimit}`);
}

async function probeDailyBaseline(
  request: APIRequestContext,
  jwt: string
): Promise<number> {
  return probeLimitBaseline(request, jwt, {
    dailyEnabled: true,
    monthlyEnabled: false,
    dailyLimit: 1,
    monthlyLimit: null,
  }, 'probe-daily');
}

async function probeMonthlyBaseline(
  request: APIRequestContext,
  jwt: string
): Promise<number> {
  return probeLimitBaseline(request, jwt, {
    dailyEnabled: false,
    monthlyEnabled: true,
    dailyLimit: null,
    monthlyLimit: 1,
  }, 'probe-monthly');
}

test.describe('Send Mail Limit', () => {
  test.beforeEach(async ({ request }) => {
    await deleteAllMailpitMessages(request);
    await resetLimitConfig(request);
  });

  test.afterEach(async ({ request }) => {
    await resetLimitConfig(request);
  });

  test('save + read roundtrip preserves all fields', async ({ request }) => {
    const config = {
      dailyEnabled: true,
      monthlyEnabled: true,
      dailyLimit: 7,
      monthlyLimit: 1234,
    };
    const save = await saveLimitConfig(request, config);
    expect(save.ok()).toBe(true);

    const read = await request.get(`${WORKER_URL}/admin/account_settings`, {
      headers: ADMIN_HEADERS,
    });
    expect(read.ok()).toBe(true);
    const body = await read.json();
    expect(body.sendMailLimitConfig).toEqual(config);
  });

  test('disabled flags coerce numeric limits to null', async ({ request }) => {
    const save = await saveLimitConfig(request, {
      dailyEnabled: false,
      monthlyEnabled: false,
      dailyLimit: 10,
      monthlyLimit: 20,
    });
    expect(save.ok()).toBe(true);

    const read = await request.get(`${WORKER_URL}/admin/account_settings`, {
      headers: ADMIN_HEADERS,
    });
    const body = await read.json();
    expect(body.sendMailLimitConfig).toEqual(DISABLED_LIMIT_CONFIG);
  });

  test('minus one is accepted as unlimited', async ({ request }) => {
    const config = {
      dailyEnabled: true,
      monthlyEnabled: true,
      dailyLimit: -1,
      monthlyLimit: -1,
    };
    const save = await saveLimitConfig(request, config);
    expect(save.ok()).toBe(true);

    const read = await request.get(`${WORKER_URL}/admin/account_settings`, {
      headers: ADMIN_HEADERS,
    });
    const body = await read.json();
    expect(body.sendMailLimitConfig).toEqual(config);
  });

  test('invalid payloads rejected with 400', async ({ request }) => {
    const cases: Array<Record<string, unknown>> = [
      { dailyEnabled: 'yes', monthlyEnabled: false, dailyLimit: null, monthlyLimit: null },
      { dailyEnabled: true, monthlyEnabled: false, dailyLimit: -2, monthlyLimit: null },
      { dailyEnabled: true, monthlyEnabled: false, dailyLimit: 1.5, monthlyLimit: null },
      { dailyEnabled: true, monthlyEnabled: false, dailyLimit: null, monthlyLimit: null },
      { dailyEnabled: false, monthlyEnabled: true, dailyLimit: null, monthlyLimit: null },
    ];
    for (const bad of cases) {
      const res = await saveLimitConfig(request, bad);
      expect(res.status(), `payload: ${JSON.stringify(bad)}`).toBe(400);
    }
  });

  test('disabled limit allows unlimited sends', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-off');
    await requestSendAccess(request, jwt);
    await resetLimitConfig(request);

    for (let i = 0; i < 3; i++) {
      const { res, listener } = await sendOneMail(request, jwt, `off${i}`);
      expect(res.ok()).toBe(true);
      await listener!.message;
    }
    await deleteAddress(request, jwt);
  });

  test('zero limit blocks sending immediately', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-zero');
    await requestSendAccess(request, jwt);

    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: false,
      dailyLimit: 0,
      monthlyLimit: null,
    });
    expect(save.ok()).toBe(true);

    const { res } = await sendOneMail(request, jwt, 'zero', {
      expectDelivery: false,
    });
    expect(res.ok()).toBe(false);
    const text = await res.text();
    expect(text).toContain('Server daily send quota has been reached');

    await deleteAddress(request, jwt);
  });

  test('daily limit blocks once reached and returns English message', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-daily');
    await requestSendAccess(request, jwt);

    const baseline = await probeDailyBaseline(request, jwt);
    const allowed = 2;
    const limit = baseline + allowed;

    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: false,
      dailyLimit: limit,
      monthlyLimit: null,
    });
    expect(save.ok()).toBe(true);

    for (let i = 0; i < allowed; i++) {
      const { res, listener } = await sendOneMail(request, jwt, `d${i}`);
      expect(res.ok(), `send #${i} should succeed`).toBe(true);
      await listener!.message;
    }

    const { res: blocked } = await sendOneMail(request, jwt, 'd-over', {
      expectDelivery: false,
    });
    expect(blocked.ok()).toBe(false);
    const text = await blocked.text();
    expect(text).toContain('Server daily send quota has been reached');

    await deleteAddress(request, jwt);
  });

  test('monthly limit blocks once reached', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-monthly');
    await requestSendAccess(request, jwt);

    const baseline = await probeMonthlyBaseline(request, jwt);
    const allowed = 2;
    const limit = baseline + allowed;

    const save = await saveLimitConfig(request, {
      dailyEnabled: false,
      monthlyEnabled: true,
      dailyLimit: null,
      monthlyLimit: limit,
    });
    expect(save.ok()).toBe(true);

    for (let i = 0; i < allowed; i++) {
      const { res, listener } = await sendOneMail(request, jwt, `m${i}`);
      expect(res.ok(), `send #${i} should succeed`).toBe(true);
      await listener!.message;
    }

    const { res: blocked } = await sendOneMail(request, jwt, 'm-over', {
      expectDelivery: false,
    });
    expect(blocked.ok()).toBe(false);
    const text = await blocked.text();
    expect(text).toContain('Server monthly send quota has been reached');

    await deleteAddress(request, jwt);
  });

  test('zh-lang header returns Chinese daily limit message', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-zh');
    await requestSendAccess(request, jwt);

    const baseline = await probeDailyBaseline(request, jwt);
    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: false,
      dailyLimit: baseline,
      monthlyLimit: null,
    });
    expect(save.ok()).toBe(true);

    const { res } = await sendOneMail(request, jwt, 'zh-over', {
      expectDelivery: false,
      lang: 'zh',
    });
    expect(res.ok()).toBe(false);
    const text = await res.text();
    expect(text).toContain('服务器今日发信次数已达上限');

    await deleteAddress(request, jwt);
  });

  test('validation failures (missing subject) do not consume quota', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-noconsume');
    await requestSendAccess(request, jwt);

    const baseline = await probeDailyBaseline(request, jwt);
    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: false,
      dailyLimit: baseline + 1,
      monthlyLimit: null,
    });
    expect(save.ok()).toBe(true);

    // Empty subject → rejected by validation BEFORE the counter increments.
    const badRes = await request.post(`${WORKER_URL}/api/send_mail`, {
      headers: { Authorization: `Bearer ${jwt}` },
      data: {
        from_name: '',
        to_name: '',
        to_mail: 'recipient@test.example.com',
        subject: '',
        content: 'no subject',
        is_html: false,
      },
    });
    expect(badRes.ok()).toBe(false);

    const { res, listener } = await sendOneMail(request, jwt, 'after-bad');
    expect(res.ok()).toBe(true);
    await listener!.message;

    await deleteAddress(request, jwt);
  });

  test('both daily + monthly enabled: tighter daily limit wins', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-both');
    await requestSendAccess(request, jwt);

    const baseline = await probeDailyBaseline(request, jwt);
    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: true,
      dailyLimit: baseline,
      monthlyLimit: baseline + 10_000,
    });
    expect(save.ok()).toBe(true);

    const { res } = await sendOneMail(request, jwt, 'both-over', {
      expectDelivery: false,
    });
    expect(res.ok()).toBe(false);
    const text = await res.text();
    expect(text).toContain('Server daily send quota has been reached');

    await deleteAddress(request, jwt);
  });

  test('minus one means unlimited at runtime', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-unlimited');
    await requestSendAccess(request, jwt);

    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: true,
      dailyLimit: -1,
      monthlyLimit: -1,
    });
    expect(save.ok()).toBe(true);

    for (let i = 0; i < 3; i++) {
      const { res, listener } = await sendOneMail(request, jwt, `unl${i}`);
      expect(res.ok(), `send #${i} should succeed`).toBe(true);
      await listener!.message;
    }

    await deleteAddress(request, jwt);
  });

  test('/admin/send_mail_by_binding returns 400 when SEND_MAIL binding is missing', async ({ request }) => {
    const res = await request.post(`${WORKER_URL}/admin/send_mail_by_binding`, {
      headers: ADMIN_HEADERS,
      data: {
        from: 'admin@test.example.com',
        to: ['recipient@test.example.com'],
        subject: 'no-binding',
        text: 'body',
      },
    });
    expect(res.status()).toBe(400);
  });

  test('/admin/send_mail_by_binding returns 200 when domain is allowed', async ({ request }) => {
    const res = await request.post(`${WORKER_URL_SEND_MAIL_DOMAIN}/admin/send_mail_by_binding`, {
      headers: ADMIN_HEADERS,
      data: {
        from: 'admin@test.example.com',
        to: ['recipient@test.example.com'],
        subject: `send-mail-domain-ok-${Date.now()}`,
        text: 'body',
      },
    });
    expect(res.ok()).toBe(true);
    expect(await res.json()).toEqual({ status: 'ok' });
  });

  test('/admin/send_mail_by_binding returns 400 when domain is not allowed', async ({ request }) => {
    const res = await request.post(`${WORKER_URL_SEND_MAIL_DOMAIN}/admin/send_mail_by_binding`, {
      headers: ADMIN_HEADERS,
      data: {
        from: 'admin@blocked.example.com',
        to: ['recipient@test.example.com'],
        subject: `send-mail-domain-blocked-${Date.now()}`,
        text: 'body',
      },
    });
    expect(res.status()).toBe(400);
    expect(await res.text()).toContain('Please enable SEND_MAIL for this domain first');
  });

  test('daily and monthly counters both increment on successful send', async ({ request }) => {
    const { jwt } = await createTestAddress(request, 'limit-both-inc');
    await requestSendAccess(request, jwt);

    const dailyBaseline = await probeDailyBaseline(request, jwt);
    const monthlyBaseline = await probeMonthlyBaseline(request, jwt);

    // Give plenty of headroom so sends succeed.
    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: true,
      dailyLimit: dailyBaseline + 10,
      monthlyLimit: monthlyBaseline + 10,
    });
    expect(save.ok()).toBe(true);

    const { res, listener } = await sendOneMail(request, jwt, 'inc');
    expect(res.ok()).toBe(true);
    await listener!.message;

    // Re-probe to confirm both counters moved forward after the successful send.
    const dailyAfter = await probeDailyBaseline(request, jwt);
    expect(dailyAfter).toBeGreaterThanOrEqual(dailyBaseline + 1);
    const monthlyAfter = await probeMonthlyBaseline(request, jwt);
    expect(monthlyAfter).toBeGreaterThanOrEqual(monthlyBaseline + 1);

    await deleteAddress(request, jwt);
  });

  test('admin /admin/send_mail also respects daily limit', async ({ request }) => {
    const { jwt, address } = await createTestAddress(request, 'limit-admin');
    await requestSendAccess(request, jwt);

    // Probe via a user-facing send to establish baseline.
    const baseline = await probeDailyBaseline(request, jwt);
    const save = await saveLimitConfig(request, {
      dailyEnabled: true,
      monthlyEnabled: false,
      dailyLimit: baseline,
      monthlyLimit: null,
    });
    expect(save.ok()).toBe(true);

    const res = await request.post(`${WORKER_URL}/admin/send_mail`, {
      headers: ADMIN_HEADERS,
      data: {
        from_name: '',
        from_mail: address,
        to_name: '',
        to_mail: 'recipient@test.example.com',
        subject: `admin-over-${Date.now()}`,
        content: 'admin blocked body',
        is_html: false,
      },
    });
    expect(res.ok()).toBe(false);
    const text = await res.text();
    expect(text).toContain('Server daily send quota has been reached');

    await deleteAddress(request, jwt);
  });
});
