import { HttpStatus } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Test, TestingModule } from '@nestjs/testing';
import { PermissionUserContext } from '../../common/access-control/permission-context.types';
import {
  DatabaseService,
  ErrorResponse,
  HashService,
  MailService,
  UploadService,
} from '../../config';
import { AccessControlService } from '../access-control/access-control.service';
import { PermissionResolverService } from '../../common/access-control/permission-resolver.service';
import { MembersHelper } from './members.helper';
import { MembersService } from './members.service';

const memberId = '11111111-1111-1111-1111-111111111111';
const otherMemberId = '22222222-2222-2222-2222-222222222222';
const ownerId = '33333333-3333-3333-3333-333333333333';

const selfCtx: PermissionUserContext = {
  sub: memberId,
  owner_id: ownerId,
  role: 'member',
  access_role_id: '44444444-4444-4444-4444-444444444444',
};

const mockMember = {
  id: memberId,
  owner_id: ownerId,
  email: 'member@example.com',
  employee_id: 'EMP001',
  first_name: 'Jane',
  last_name: 'Doe',
  status: 'active',
  role: 'member',
  work_experiences: [],
  educations: [],
  department: null,
  created_at: new Date('2024-01-01'),
  updated_at: new Date('2024-01-01'),
};

describe('MembersService', () => {
  let service: MembersService;
  let permissionResolver: { resolveForUser: jest.Mock };
  let db: {
    members: {
      findUnique: jest.Mock;
      findMany: jest.Mock;
      update: jest.Mock;
    };
    work_experiences: { deleteMany: jest.Mock; createMany: jest.Mock };
    educations: { deleteMany: jest.Mock; createMany: jest.Mock };
  };

  beforeEach(async () => {
    permissionResolver = {
      resolveForUser: jest.fn(),
    };

    db = {
      members: {
        findUnique: jest.fn(),
        findMany: jest.fn(),
        update: jest.fn(),
      },
      work_experiences: {
        deleteMany: jest.fn(),
        createMany: jest.fn(),
      },
      educations: {
        deleteMany: jest.fn(),
        createMany: jest.fn(),
      },
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        MembersService,
        { provide: DatabaseService, useValue: db },
        { provide: HashService, useValue: { hashPassword: jest.fn() } },
        { provide: JwtService, useValue: { sign: jest.fn() } },
        { provide: MailService, useValue: { sendMail: jest.fn() } },
        { provide: UploadService, useValue: { moveImage: jest.fn(), removeImage: jest.fn() } },
        { provide: MembersHelper, useValue: {} },
        { provide: AccessControlService, useValue: {} },
        { provide: PermissionResolverService, useValue: permissionResolver },
      ],
    }).compile();

    service = module.get<MembersService>(MembersService);
  });

  const deniedPermissions = {
    'members.access': 'false',
    'members.view': 'none',
    'members.update': 'none',
  };

  describe('getMemberById', () => {
    it('allows a member to read their own profile without members.access', async () => {
      db.members.findUnique.mockResolvedValue(mockMember);

      const result = await service.getMemberById(memberId, ownerId, selfCtx);

      expect(permissionResolver.resolveForUser).not.toHaveBeenCalled();
      expect(result.status).toBe(HttpStatus.OK);
      expect(result.message).toBe('Member retrieved successfully');
      expect(db.members.findUnique).toHaveBeenCalledWith({
        where: { id: memberId, owner_id: ownerId },
        include: {
          work_experiences: true,
          educations: true,
          department: true,
        },
      });
    });

    it('denies reading another member when members.access is disabled', async () => {
      permissionResolver.resolveForUser.mockResolvedValue(deniedPermissions);

      await expect(
        service.getMemberById(otherMemberId, ownerId, selfCtx),
      ).rejects.toBeInstanceOf(ErrorResponse);

      expect(db.members.findUnique).not.toHaveBeenCalled();
    });
  });

  describe('updateMember', () => {
    beforeEach(() => {
      db.members.findUnique
        .mockResolvedValueOnce(mockMember)
        .mockResolvedValueOnce({
          ...mockMember,
          first_name: 'Updated',
          work_experiences: [],
          educations: [],
        });
      db.members.update.mockResolvedValue({});
    });

    it('allows a member to update their own profile without members.access', async () => {
      const result = await service.updateMember(
        memberId,
        { first_name: 'Updated', phone: '1234567890' },
        ownerId,
        selfCtx,
      );

      expect(permissionResolver.resolveForUser).not.toHaveBeenCalled();
      expect(db.members.update).toHaveBeenCalledWith(
        expect.objectContaining({
          where: { id: memberId },
          data: expect.objectContaining({ first_name: 'Updated', phone: '1234567890' }),
        }),
      );
      expect(result.status).toBe(HttpStatus.OK);
    });

    it('strips admin-only fields from self-updates', async () => {
      await service.updateMember(
        memberId,
        {
          first_name: 'Updated',
          status: 'terminated',
          employee_id: 'HACKED',
          access_role_id: '55555555-5555-5555-5555-555555555555',
          department_id: '66666666-6666-6666-6666-666666666666',
        },
        ownerId,
        selfCtx,
      );

      const updateCall = db.members.update.mock.calls[0][0];
      expect(updateCall.data).toEqual(
        expect.objectContaining({ first_name: 'Updated' }),
      );
      expect(updateCall.data.status).toBeUndefined();
      expect(updateCall.data.employee_id).toBeUndefined();
      expect(updateCall.data.access_role_id).toBeUndefined();
      expect(updateCall.data.department).toBeUndefined();
    });

    it('denies updating another member when members.access is disabled', async () => {
      permissionResolver.resolveForUser.mockResolvedValue(deniedPermissions);

      await expect(
        service.updateMember(
          otherMemberId,
          { first_name: 'Updated' },
          ownerId,
          selfCtx,
        ),
      ).rejects.toBeInstanceOf(ErrorResponse);

      expect(db.members.update).not.toHaveBeenCalled();
    });
  });

  describe('getMembers', () => {
    it('still requires members.access for listing members', async () => {
      permissionResolver.resolveForUser.mockResolvedValue(deniedPermissions);

      await expect(
        service.getMembers(ownerId, selfCtx),
      ).rejects.toBeInstanceOf(ErrorResponse);

      expect(db.members.findMany).not.toHaveBeenCalled();
    });
  });
});
