import { BadRequestException, ConflictException } from '@nestjs/common';
import type { SignInService } from '@nestjs/authentication';
import { Prisma } from '../../generated/prisma/client.js';
import type { PrismaService } from '../../database/prisma.service.js';
import type { PasswordService } from '../auth/password.service.js';
import { SettingsService } from './settings.service.js';

describe('SettingsService', () => {
  let service: SettingsService;
  let upsert: ReturnType<typeof vi.fn>;
  let userCount: ReturnType<typeof vi.fn>;
  let userCreate: ReturnType<typeof vi.fn>;
  let categoryFindFirst: ReturnType<typeof vi.fn>;
  let categoryCreate: ReturnType<typeof vi.fn>;
  let categoryUpdate: ReturnType<typeof vi.fn>;
  let signIn: ReturnType<typeof vi.fn>;

  const store = {
    store: {
      storeName: 'Xenmesh Fashion House',
      contactEmail: 'hello@xenmesh.com',
      phone: '01912444216',
      location: 'Dhaka',
      facebookUrl: 'https://facebook.com/xenmesh',
    },
    admin: { firstName: 'Imran', email: 'Owner@Xenmesh.com', password: 'a-long-password' },
    categories: ['Panjabi', 'Accessories', 'Panjabi'],
  };

  beforeEach(() => {
    upsert = vi.fn().mockResolvedValue({
      id: 'store',
      storeName: 'Xenmesh Fashion House',
      contactEmail: 'xen@gmail.com',
      phone: '01912444216',
      location: 'Dhaka, Bangladesh',
      deliveryCharge: new Prisma.Decimal(60),
      setupCompleted: false,
    });
    userCount = vi.fn().mockResolvedValue(0);
    userCreate = vi.fn().mockImplementation(({ data }) => Promise.resolve({
      id: 1,
      lastName: null,
      phone: null,
      ...data,
    }));
    categoryFindFirst = vi.fn().mockImplementation(({ where }) =>
      Promise.resolve(where.OR[0].name === 'Panjabi' ? { id: 2 } : null));
    categoryCreate = vi.fn().mockResolvedValue({});
    categoryUpdate = vi.fn().mockResolvedValue({});
    signIn = vi.fn().mockResolvedValue(undefined);
    const prisma = {
      storeSettings: { upsert },
      user: { count: userCount, create: userCreate },
      category: { findFirst: categoryFindFirst, create: categoryCreate, update: categoryUpdate },
      $transaction: vi.fn((callback: (tx: unknown) => unknown) => callback(prisma)),
    };
    service = new SettingsService(
      prisma as unknown as PrismaService,
      { hash: vi.fn().mockResolvedValue('hashed') } as unknown as PasswordService,
      { signIn } as unknown as SignInService,
    );
  });

  it('seeds the default public details when none exist', async () => {
    const settings = await service.getSettings();
    expect(settings).toMatchObject({
      storeName: 'Xenmesh Fashion House',
      deliveryCharge: 60,
    });
    expect(upsert).toHaveBeenCalledWith(expect.objectContaining({
      where: { id: 'store' },
      update: {},
    }));
  });

  it('saves only the fields an admin sent', async () => {
    await service.updateSettings({
      storeName: 'Xenmesh Studio',
      whatsappNumber: '',
      instagramUrl: 'https://instagram.com/xenmesh',
    });
    expect(upsert).toHaveBeenCalledWith(expect.objectContaining({
      update: {
        storeName: 'Xenmesh Studio',
        whatsappNumber: null,
        instagramUrl: 'https://instagram.com/xenmesh',
      },
    }));
  });

  it('rejects malformed contact details and unsafe links', async () => {
    await expect(service.updateSettings({ contactEmail: 'not-an-email' }))
      .rejects.toBeInstanceOf(BadRequestException);
    await expect(service.updateSettings({ facebookUrl: 'javascript:alert(1)' }))
      .rejects.toBeInstanceOf(BadRequestException);
    expect(upsert).not.toHaveBeenCalled();
  });

  it('creates the owner account, store and published categories on first run', async () => {
    const result = await service.completeSetup(store, false);

    expect(userCreate).toHaveBeenCalledWith({
      data: {
        email: 'owner@xenmesh.com',
        passwordHash: 'hashed',
        firstName: 'Imran',
        role: 'ADMIN',
      },
    });
    // The seeded Panjabi draft is published; Accessories is new.
    expect(categoryUpdate).toHaveBeenCalledWith({
      where: { id: 2 },
      data: { status: 'PUBLISHED', sortOrder: 0 },
    });
    expect(categoryCreate).toHaveBeenCalledWith({
      data: { name: 'Accessories', slug: 'accessories', sortOrder: 1, status: 'PUBLISHED' },
    });
    expect(upsert).toHaveBeenLastCalledWith(expect.objectContaining({
      update: expect.objectContaining({ setupCompleted: true, storeName: 'Xenmesh Fashion House' }),
    }));
    expect(signIn).toHaveBeenCalledWith('1', { method: 'password' });
    expect(result.user?.role).toBe('ADMIN');
  });

  it('refuses an anonymous setup once an admin exists', async () => {
    userCount.mockResolvedValue(1);
    await expect(service.completeSetup(store, false)).rejects.toBeInstanceOf(ConflictException);
    expect(userCreate).not.toHaveBeenCalled();
  });

  it('lets a signed-in admin finish setup without creating another account', async () => {
    userCount.mockResolvedValue(1);
    const result = await service.completeSetup({ ...store, admin: undefined }, true);
    expect(userCreate).not.toHaveBeenCalled();
    expect(result.user).toBeNull();
  });

  it('cannot be run twice', async () => {
    upsert.mockResolvedValue({ deliveryCharge: new Prisma.Decimal(0), setupCompleted: true });
    await expect(service.completeSetup(store, true)).rejects.toBeInstanceOf(ConflictException);
  });
});
