import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { JwtService } from '@nestjs/jwt';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { UserService } from '../user/user.service';
import { ParentsService } from '../parents/parents.service';
import { DossiersService } from '../dossiers/dossiers.service';
import { AppConfigService } from 'src/modules/config/config.service';
import { MailService } from 'src/modules/mail/mail.service';
import { NumeroDossierService } from 'src/modules/numero-dossier/numero-dossier.service';
import { Parents } from 'src/entities/parents.entity';
import { Users, RoleType, StatutUtilisateurType } from 'src/entities/users.entity';
import { Children } from 'src/entities/children.entity';
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';

describe('AuthService (#118 create-password API)', () => {
  let service: AuthService;
  const usersRepoMock = {
    findOne: jest.fn(),
    createQueryBuilder: jest.fn(),
    update: jest.fn(),
    manager: { transaction: jest.fn() },
  };

  const userServiceMock = {
    findByTokenReprise: jest.fn(),
  };

  const parentsServiceMock = {
    getDossierFamilleByNumero: jest.fn(),
    getFamilyUserIds: jest.fn(),
  };

  const dossiersServiceMock = {
    getDossierByNumero: jest.fn(),
  };

  const mailServiceMock = {
    sendPasswordResetEmail: jest.fn().mockResolvedValue(undefined),
  };

  const appConfigMock = {
    get: jest.fn().mockReturnValue(7),
  };

  beforeEach(async () => {
    jest.clearAllMocks();
    const qbChain: any = {};
    qbChain.where = jest.fn().mockReturnValue(qbChain);
    qbChain.select = jest.fn().mockReturnValue(qbChain);
    qbChain.getOne = jest.fn().mockResolvedValue(null);
    qbChain.update = jest.fn().mockReturnValue(qbChain);
    qbChain.set = jest.fn().mockReturnValue(qbChain);
    qbChain.andWhere = jest.fn().mockReturnValue(qbChain);
    qbChain.execute = jest.fn().mockResolvedValue({ affected: 0 });
    usersRepoMock.createQueryBuilder.mockReturnValue(qbChain);
    usersRepoMock.update.mockResolvedValue({ affected: 1, raw: [] });

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AuthService,
        { provide: UserService, useValue: userServiceMock },
        { provide: ParentsService, useValue: parentsServiceMock },
        { provide: DossiersService, useValue: dossiersServiceMock },
        { provide: JwtService, useValue: {} },
        { provide: ConfigService, useValue: { get: jest.fn() } },
        { provide: AppConfigService, useValue: appConfigMock },
        { provide: MailService, useValue: mailServiceMock },
        { provide: NumeroDossierService, useValue: {} },
        { provide: getRepositoryToken(Parents), useValue: {} },
        { provide: getRepositoryToken(Users), useValue: usersRepoMock },
        { provide: getRepositoryToken(Children), useValue: {} },
        { provide: getRepositoryToken(AssistanteMaternelle), useValue: {} },
      ],
    }).compile();

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

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('returns 404-like error when verify token is missing', async () => {
    await expect(service.verifyCreatePasswordToken('')).rejects.toThrow(NotFoundException);
  });

  it('returns valid=true when token exists and is not expired', async () => {
    usersRepoMock.findOne.mockResolvedValue({
      id: 'u1',
      password: null,
      token_creation_mdp_expire_le: new Date(Date.now() + 60_000),
    });

    await expect(service.verifyCreatePasswordToken('tok-123')).resolves.toEqual({
      valid: true,
      message: 'Token valide',
    });
  });

  describe('#127 forgot / reset password', () => {
    it('requestPasswordReset returns generic message when email unknown', async () => {
      const r = await service.requestPasswordReset('unknown@test.fr');
      expect(r.message).toContain('Si cette adresse');
      expect(mailServiceMock.sendPasswordResetEmail).not.toHaveBeenCalled();
    });

    it('requestPasswordReset sends mail when user has password', async () => {
      usersRepoMock.createQueryBuilder.mockReturnValueOnce({
        where: jest.fn().mockReturnThis(),
        select: jest.fn().mockReturnThis(),
        getOne: jest.fn().mockResolvedValue({
          id: 'u1',
          email: 'a@test.fr',
          prenom: 'A',
          nom: 'B',
          password: 'hashed',
        }),
      });
      const r = await service.requestPasswordReset('a@test.fr');
      expect(r.message).toContain('Si cette adresse');
      expect(usersRepoMock.update).toHaveBeenCalled();
      expect(mailServiceMock.sendPasswordResetEmail).toHaveBeenCalledWith(
        expect.objectContaining({ email: 'a@test.fr', token: expect.any(String) }),
      );
    });

    it('resetPasswordWithResetToken throws when token unknown', async () => {
      usersRepoMock.findOne.mockResolvedValue(null);
      await expect(service.resetPasswordWithResetToken('bad', 'Password1')).rejects.toThrow(
        NotFoundException,
      );
    });

    it('resetPasswordWithResetToken succeeds when update affects row', async () => {
      usersRepoMock.findOne.mockResolvedValue({
        id: 'u1',
        password: 'oldhash',
        password_reset_expires: new Date(Date.now() + 60_000),
      });
      const exec = jest.fn().mockResolvedValue({ affected: 1 });
      const chain: any = {};
      chain.update = jest.fn().mockReturnValue(chain);
      chain.set = jest.fn().mockReturnValue(chain);
      chain.where = jest.fn().mockReturnValue(chain);
      chain.andWhere = jest.fn().mockReturnValue(chain);
      chain.execute = exec;
      usersRepoMock.createQueryBuilder.mockReturnValueOnce(chain);

      const r = await service.resetPasswordWithResetToken('good-token', 'Password1');
      expect(r.message.toLowerCase()).toContain('mis à jour');
      expect(exec).toHaveBeenCalled();
    });
  });

  describe('Reprise après refus (#112)', () => {
    const token = '11111111-1111-1111-1111-111111111111';

    it('getRepriseDossier throws when token invalid', async () => {
      userServiceMock.findByTokenReprise.mockResolvedValue(null);
      await expect(service.getRepriseDossier(token)).rejects.toThrow(NotFoundException);
    });

    it('getRepriseDossier returns famille complète pour parent', async () => {
      userServiceMock.findByTokenReprise.mockResolvedValue({
        id: 'p1',
        email: 'claire@test.fr',
        prenom: 'Claire',
        nom: 'MARTIN',
        role: RoleType.PARENT,
        numero_dossier: '2026-000021',
        statut: StatutUtilisateurType.REFUSE,
      });
      parentsServiceMock.getDossierFamilleByNumero.mockResolvedValue({
        numero_dossier: '2026-000021',
        parents: [{ user_id: 'p1', email: 'claire@test.fr', statut: StatutUtilisateurType.REFUSE }],
        enfants: [{ id: 'e1', first_name: 'Emma', status: 'sans_garde' }],
        texte_motivation: 'Motivation test',
      });

      const result = await service.getRepriseDossier(token);
      expect(result.parents).toHaveLength(1);
      expect(result.enfants).toHaveLength(1);
      expect(result.texte_motivation).toBe('Motivation test');
      expect(result.numero_dossier).toBe('2026-000021');
    });

    it('getRepriseDossier returns fiche AM complète', async () => {
      userServiceMock.findByTokenReprise.mockResolvedValue({
        id: 'am1',
        email: 'am@test.fr',
        role: RoleType.ASSISTANTE_MATERNELLE,
        numero_dossier: '2026-000003',
      });
      dossiersServiceMock.getDossierByNumero.mockResolvedValue({
        type: 'am',
        dossier: {
          numero_dossier: '2026-000003',
          user: {
            id: 'am1',
            email: 'am@test.fr',
            prenom: 'Marie',
            nom: 'DUPONT',
            statut: StatutUtilisateurType.REFUSE,
          },
          numero_agrement: 'AGR-1',
          nir: '123456789012345',
          biographie: 'Bio',
          nb_max_enfants: 4,
          place_disponible: 2,
        },
      });

      const result = await service.getRepriseDossier(token);
      expect(result.numero_agrement).toBe('AGR-1');
      expect(result.biographie).toBe('Bio');
      expect(result.nb_max_enfants).toBe(4);
    });
  });
});
