import {
  BadRequestException,
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import { RoleType, Users } from 'src/entities/users.entity';
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
import { AmChildren } from 'src/entities/am_children.entity';
import { Children, StatutEnfantType } from 'src/entities/children.entity';
import { CreateAssistanteDto } from '../user/dto/create_assistante.dto';
import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto';
import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto';
import { validateNir } from 'src/common/utils/nir.util';

const AM_CHILDREN_RELATIONS = ['user', 'amChildren', 'amChildren.child'] as const;

@Injectable()
export class AssistantesMaternellesService {
  constructor(
    @InjectRepository(AssistanteMaternelle)
    private readonly assistantesMaternelleRepository: Repository<AssistanteMaternelle>,
    @InjectRepository(Users)
    private readonly usersRepository: Repository<Users>,
    @InjectRepository(AmChildren)
    private readonly amChildrenRepository: Repository<AmChildren>,
    @InjectRepository(Children)
    private readonly childrenRepository: Repository<Children>,
  ) {}

  async create(dto: CreateAssistanteDto): Promise<AssistanteMaternelle> {
    const user = await this.usersRepository.findOneBy({ id: dto.user_id });
    if (!user) throw new NotFoundException('Utilisateur introuvable');
    if (user.role !== RoleType.ASSISTANTE_MATERNELLE) {
      throw new BadRequestException('Accès réservé aux assistantes maternelles');
    }

    const exist = await this.assistantesMaternelleRepository.findOneBy({ user_id: dto.user_id });
    if (exist) throw new ConflictException('Assistante maternelle déjà existante');

    const entity = this.assistantesMaternelleRepository.create({
      user_id: dto.user_id,
      user: { ...user, role: RoleType.ASSISTANTE_MATERNELLE },
      approval_number: dto.approval_number,
      nir: dto.nir,
      max_children: dto.max_children,
      biography: dto.biography,
      available: dto.available ?? true,
      residence_city: dto.residence_city,
      agreement_date: dto.agreement_date ? new Date(dto.agreement_date) : undefined,
      years_experience: dto.years_experience,
      specialty: dto.specialty,
      places_available: dto.places_available,
    });

    return this.assistantesMaternelleRepository.save(entity);
  }

  async findAll(): Promise<AssistanteMaternelle[]> {
    return this.assistantesMaternelleRepository.find({
      relations: [...AM_CHILDREN_RELATIONS],
    });
  }

  async findOne(user_id: string): Promise<AssistanteMaternelle> {
    const assistante = await this.assistantesMaternelleRepository.findOne({
      where: { user_id },
      relations: [...AM_CHILDREN_RELATIONS],
    });
    if (!assistante) throw new NotFoundException('Assistante maternelle introuvable');
    return assistante;
  }

  async update(id: string, dto: UpdateAssistanteDto): Promise<AssistanteMaternelle> {
    await this.assistantesMaternelleRepository.update(id, dto);
    return this.findOne(id);
  }

  /**
   * Mise à jour fiche AM (identité + champs pro) par admin/gestionnaire. Ticket #131.
   */
  async updateFicheAdmin(amUserId: string, dto: UpdateAmFicheAdminDto): Promise<AssistanteMaternelle> {
    const am = await this.findOne(amUserId);
    const user = am.user;

    if (dto.email && dto.email !== user.email) {
      const existing = await this.usersRepository.findOne({ where: { email: dto.email } });
      if (existing && existing.id !== user.id) {
        throw new ConflictException('Cet email est déjà utilisé');
      }
      user.email = dto.email;
    }

    if (dto.nom !== undefined) user.nom = dto.nom;
    if (dto.prenom !== undefined) user.prenom = dto.prenom;
    if (dto.telephone !== undefined) user.telephone = dto.telephone;
    if (dto.adresse !== undefined) user.adresse = dto.adresse;
    if (dto.ville !== undefined) user.ville = dto.ville;
    if (dto.code_postal !== undefined) user.code_postal = dto.code_postal;
    if (dto.statut !== undefined) user.statut = dto.statut;
    if (dto.date_naissance !== undefined) {
      user.date_naissance = dto.date_naissance ? new Date(dto.date_naissance) : undefined;
    }
    if (dto.lieu_naissance_ville !== undefined) {
      user.lieu_naissance_ville = dto.lieu_naissance_ville || undefined;
    }
    if (dto.lieu_naissance_pays !== undefined) {
      user.lieu_naissance_pays = dto.lieu_naissance_pays || undefined;
    }

    await this.usersRepository.save(user);

    const amPatch: Partial<AssistanteMaternelle> = {};
    if (dto.approval_number !== undefined) amPatch.approval_number = dto.approval_number;
    if (dto.residence_city !== undefined) amPatch.residence_city = dto.residence_city;
    if (dto.max_children !== undefined) amPatch.max_children = dto.max_children;
    if (dto.places_available !== undefined) amPatch.places_available = dto.places_available;
    if (dto.biography !== undefined) amPatch.biography = dto.biography;
    if (dto.available !== undefined) amPatch.available = dto.available;
    if (dto.agreement_date !== undefined) {
      amPatch.agreement_date = dto.agreement_date ? new Date(dto.agreement_date) : undefined;
    }

    if (dto.nir !== undefined) {
      const nirNormalized = dto.nir.replace(/\s/g, '').toUpperCase();
      if (nirNormalized) {
        const dateNaissanceForNir =
          dto.date_naissance ??
          (user.date_naissance instanceof Date
            ? user.date_naissance.toISOString().slice(0, 10)
            : user.date_naissance
              ? String(user.date_naissance).slice(0, 10)
              : undefined);
        const nirValidation = validateNir(nirNormalized, {
          dateNaissance: dateNaissanceForNir,
        });
        if (!nirValidation.valid) {
          throw new BadRequestException(nirValidation.error || 'NIR invalide');
        }
        const nirDejaUtilise = await this.assistantesMaternelleRepository.findOne({
          where: { nir: nirNormalized },
        });
        if (nirDejaUtilise && nirDejaUtilise.user_id !== amUserId) {
          throw new ConflictException(
            'Un compte assistante maternelle avec ce numéro NIR existe déjà.',
          );
        }
        amPatch.nir = nirNormalized;
      }
      // NIR vide : ne pas effacer (colonne NOT NULL en BDD) — le front renvoie toujours la clé.
    }

    if (Object.keys(amPatch).length > 0) {
      await this.assistantesMaternelleRepository.update(amUserId, amPatch);
    }

    return this.findOne(amUserId);
  }

  /**
   * Rattacher un enfant à une AM (placement actif). Ticket #131.
   * Passe le statut enfant à `garde` (sauf a_naitre / scolarise).
   */
  async attachEnfant(amUserId: string, enfantId: string, createdBy?: Users): Promise<AssistanteMaternelle> {
    const am = await this.findOne(amUserId);

    const existingForAm = await this.amChildrenRepository.findOne({
      where: { amId: amUserId, enfantId, date_fin: IsNull() },
    });
    if (existingForAm) {
      throw new ConflictException('Cet enfant est déjà rattaché à cette assistante maternelle');
    }

    const child = await this.childrenRepository.findOne({ where: { id: enfantId } });
    if (!child) {
      throw new NotFoundException('Enfant introuvable');
    }

    const activeForChild = await this.amChildrenRepository.findOne({
      where: { enfantId, date_fin: IsNull() },
    });
    if (activeForChild && activeForChild.amId !== amUserId) {
      throw new ConflictException(
        'Cet enfant est déjà en garde chez une autre assistante maternelle',
      );
    }

    const activeCount = await this.amChildrenRepository.count({
      where: { amId: amUserId, date_fin: IsNull() },
    });
    if (am.max_children != null && activeCount >= am.max_children) {
      throw new BadRequestException(
        `Capacité maximale atteinte (${am.max_children} enfant(s))`,
      );
    }

    await this.amChildrenRepository.save(
      this.amChildrenRepository.create({
        amId: amUserId,
        enfantId,
        date_debut: new Date(),
        cree_par: createdBy?.id,
      }),
    );

    await this.applyGardeStatusOnAttach(child);

    return this.findOne(amUserId);
  }

  /**
   * Clôturer le placement AM ↔ enfant. Ticket #131.
   * Repasse l'enfant en `sans_garde` s'il n'a plus de placement actif.
   */
  async detachEnfant(amUserId: string, enfantId: string): Promise<AssistanteMaternelle> {
    await this.findOne(amUserId);

    const link = await this.amChildrenRepository.findOne({
      where: { amId: amUserId, enfantId, date_fin: IsNull() },
      relations: ['child'],
    });
    if (!link) {
      throw new NotFoundException('Lien assistante maternelle-enfant introuvable');
    }

    link.date_fin = new Date();
    await this.amChildrenRepository.save(link);

    const remaining = await this.amChildrenRepository.count({
      where: { enfantId, date_fin: IsNull() },
    });
    if (remaining === 0 && link.child) {
      await this.applySansGardeStatusOnDetach(link.child);
    }

    return this.findOne(amUserId);
  }

  private async applyGardeStatusOnAttach(child: Children): Promise<void> {
    if (
      child.status === StatutEnfantType.A_NAITRE ||
      child.status === StatutEnfantType.SCOLARISE
    ) {
      return;
    }
    child.status = StatutEnfantType.GARDE;
    await this.childrenRepository.save(child);
  }

  private async applySansGardeStatusOnDetach(child: Children): Promise<void> {
    if (
      child.status === StatutEnfantType.A_NAITRE ||
      child.status === StatutEnfantType.SCOLARISE
    ) {
      return;
    }
    child.status = StatutEnfantType.SANS_GARDE;
    await this.childrenRepository.save(child);
  }

  async remove(id: string): Promise<{ message: string }> {
    await this.assistantesMaternelleRepository.delete(id);
    return { message: 'Assistante maternelle supprimée' };
  }
}
