import { describe, it, expect, beforeAll, vi } from 'vitest';
import { createPgliteClient } from '@platform-modules/db/pglite';
import {
  createDbNotificationStore,
  notificationsSchema,
  type NotificationsSchema,
} from '@platform-modules/notifications/inbox';
import type { Querier } from '@platform-modules/db';
import { createCustomEngine, customAuthSchema } from '@platform-modules/auth/engine-custom';
import type { UserAdminEngine } from '@platform-modules/auth';
import authSchemaSql from '../db/auth-schema.sql?raw';
import { applyNotificationsSchema } from './install.js';
import {
  COMMENT_CREATED_EVENT,
  notifyAdmins,
  resolveAdminRecipientIds,
  SUBMISSION_CREATED_EVENT,
} from './notifications.js';

const SECRET = 'test-jwt-secret';
const PEPPER = { currentVersion: 'v1' as const, secrets: { v1: 'test-pepper' } };

const mocks = vi.hoisted(() => {
  let db: Querier<NotificationsSchema>;
  let engine: UserAdminEngine;
  return {
    getDb: () => db,
    setDb: (d: Querier<NotificationsSchema>) => {
      db = d;
    },
    getEngine: () => engine,
    setEngine: (e: UserAdminEngine) => {
      engine = e;
    },
  };
});

vi.mock('./db.js', async (importOriginal) => {
  const actual = await importOriginal<typeof import('./db.js')>();
  return {
    ...actual,
    getFullDb: () => ({ db: mocks.getDb(), dialect: 'postgres' }),
  };
});

describe('resolveAdminRecipientIds', () => {
  beforeAll(async () => {
    const authDb = createPgliteClient({ schema: customAuthSchema });
    for (const stmt of authSchemaSql.split(';').map((s) => s.trim()).filter(Boolean)) {
      await authDb.execute(stmt);
    }
    const eng = createCustomEngine({ db: authDb, jwtSecrets: [SECRET], pepper: PEPPER });
    await eng.createUser({ email: 'admin1@x.test', password: 'pw-admin-11111', roles: ['admin'] });
    await eng.createUser({ email: 'admin2@x.test', password: 'pw-admin-22222', roles: ['admin'] });
    const disabled = await eng.createUser({
      email: 'disabled@x.test',
      password: 'pw-disabled1',
      roles: ['admin'],
    });
    await eng.disableUser(disabled.userId);
    await eng.createUser({ email: 'editor@x.test', password: 'pw-editor-123', roles: ['editor'] });
    await eng.createUser({ email: 'viewer@x.test', password: 'pw-viewer-123', roles: ['viewer'] });
    mocks.setEngine(eng);
  });

  it('returns only active admins — disabled admin excluded', async () => {
    const ids = await resolveAdminRecipientIds(mocks.getEngine());
    expect(ids).toHaveLength(2);
    const page = await mocks.getEngine().listUsers({ limit: 50, offset: 0 });
    const activeAdminEmails = page.users
      .filter((u) => u.status === 'active' && u.roles.includes('admin'))
      .map((u) => u.email)
      .sort();
    expect(activeAdminEmails).toEqual(['admin1@x.test', 'admin2@x.test']);
    for (const id of ids) {
      const user = page.users.find((u) => u.id === id);
      expect(user?.status).toBe('active');
      expect(user?.roles).toContain('admin');
    }
  });
});

describe('notifyAdmins', () => {
  let adminIds: string[];

  beforeAll(async () => {
    const notifDb = createPgliteClient({ schema: notificationsSchema });
    await applyNotificationsSchema(notifDb as unknown as Querier);
    mocks.setDb(notifDb);

    const authDb = createPgliteClient({ schema: customAuthSchema });
    for (const stmt of authSchemaSql.split(';').map((s) => s.trim()).filter(Boolean)) {
      await authDb.execute(stmt);
    }
    const eng = createCustomEngine({ db: authDb, jwtSecrets: [SECRET], pepper: PEPPER });
    await eng.createUser({ email: 'a1@x.test', password: 'pw-admin-11111', roles: ['admin'] });
    await eng.createUser({ email: 'a2@x.test', password: 'pw-admin-22222', roles: ['admin'] });
    mocks.setEngine(eng);
    adminIds = await resolveAdminRecipientIds(eng);
    expect(adminIds).toHaveLength(2);
  });

  it('N active admins → N inbox rows', async () => {
    await notifyAdmins(
      { DATABASE_URL: 'postgres://local/test' },
      mocks.getEngine(),
      SUBMISSION_CREATED_EVENT,
    );
    const store = createDbNotificationStore(mocks.getDb());
    for (const userId of adminIds) {
      const page = await store.list(userId);
      const row = page.items.find((i) => i.kind === 'submission' && i.title === 'New contact submission');
      expect(row).toBeTruthy();
      expect(row?.href).toBe('/admin/submissions');
    }
  });

  it('comment.created uses static comment payload', async () => {
    await notifyAdmins(
      { DATABASE_URL: 'postgres://local/test' },
      mocks.getEngine(),
      COMMENT_CREATED_EVENT,
    );
    const store = createDbNotificationStore(mocks.getDb());
    const page = await store.list(adminIds[0]!);
    const commentRow = page.items.find((i) => i.kind === 'comment');
    expect(commentRow?.title).toBe('New comment pending moderation');
    expect(commentRow?.href).toBe('/admin/comments');
  });

  it('one store.put rejects → others still inserted, notifyAdmins resolves', async () => {
    const inbox = await import('@platform-modules/notifications/inbox');
    const realCreate = inbox.createDbNotificationStore;
    let putCalls = 0;
    vi.spyOn(inbox, 'createDbNotificationStore').mockImplementation((db) => {
      const store = realCreate(db);
      const realPut = store.put.bind(store);
      return {
        ...store,
        put: async (input) => {
          putCalls += 1;
          if (putCalls === 1) throw new Error('simulated put failure');
          return realPut(input);
        },
      };
    });

    await expect(
      notifyAdmins({ DATABASE_URL: 'postgres://local/test' }, mocks.getEngine(), SUBMISSION_CREATED_EVENT),
    ).resolves.toBeUndefined();

    vi.restoreAllMocks();
    const store = createDbNotificationStore(mocks.getDb());
    let withNewSubmission = 0;
    for (const userId of adminIds) {
      const page = await store.list(userId);
      if (page.items.some((i) => i.title === 'New contact submission')) withNewSubmission += 1;
    }
    expect(withNewSubmission).toBeGreaterThanOrEqual(1);
  });

  it('recipient-resolution throw → swallowed, resolves', async () => {
    const badEngine = {
      listUsers: vi.fn(async () => {
        throw new Error('listUsers down');
      }),
    } as unknown as UserAdminEngine;
    await expect(
      notifyAdmins({ DATABASE_URL: 'postgres://local/test' }, badEngine, SUBMISSION_CREATED_EVENT),
    ).resolves.toBeUndefined();
  });

  it('zero admins → no-op, resolves', async () => {
    const authDb = createPgliteClient({ schema: customAuthSchema });
    for (const stmt of authSchemaSql.split(';').map((s) => s.trim()).filter(Boolean)) {
      await authDb.execute(stmt);
    }
    const eng = createCustomEngine({ db: authDb, jwtSecrets: [SECRET], pepper: PEPPER });
    await eng.createUser({ email: 'only-editor@x.test', password: 'pw-editor-123', roles: ['editor'] });
    await expect(
      notifyAdmins({ DATABASE_URL: 'postgres://local/test' }, eng, SUBMISSION_CREATED_EVENT),
    ).resolves.toBeUndefined();
  });
});
