import {
    ConflictException,
    Injectable,
    UnauthorizedException,
    BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserService } from '../user/user.service';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
import * as fs from 'fs/promises';
import * as path from 'path';
import { RegisterDto } from './dto/register.dto';
import { RegisterParentCompletDto } from './dto/register-parent-complet.dto';
import { RegisterAMCompletDto } from './dto/register-am-complet.dto';
import { ConfigService } from '@nestjs/config';
import { RoleType, StatutUtilisateurType, Users } from 'src/entities/users.entity';
import { Parents } from 'src/entities/parents.entity';
import { Children, StatutEnfantType } from 'src/entities/children.entity';
import { ParentsChildren } from 'src/entities/parents_children.entity';
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
import { LoginDto } from './dto/login.dto';
import { AppConfigService } from 'src/modules/config/config.service';
import { validateNir } from 'src/common/utils/nir.util';
import { NumeroDossierService } from 'src/modules/numero-dossier/numero-dossier.service';

@Injectable()
export class AuthService {
    constructor(
        private readonly usersService: UserService,
        private readonly jwtService: JwtService,
        private readonly configService: ConfigService,
        private readonly appConfigService: AppConfigService,
        private readonly numeroDossierService: NumeroDossierService,
        @InjectRepository(Parents)
        private readonly parentsRepo: Repository<Parents>,
        @InjectRepository(Users)
        private readonly usersRepo: Repository<Users>,
        @InjectRepository(Children)
        private readonly childrenRepo: Repository<Children>,
    ) { }

    /**
     * Génère un access_token et un refresh_token
     */
    async generateTokens(userId: string, email: string, role: RoleType) {
        const accessSecret = this.configService.get<string>('jwt.accessSecret');
        const accessExpiresIn = this.configService.get<string>('jwt.accessExpiresIn');
        const refreshSecret = this.configService.get<string>('jwt.refreshSecret');
        const refreshExpiresIn = this.configService.get<string>('jwt.refreshExpiresIn');

        const [accessToken, refreshToken] = await Promise.all([
            this.jwtService.signAsync({ sub: userId, email, role }, { secret: accessSecret, expiresIn: accessExpiresIn }),
            this.jwtService.signAsync({ sub: userId }, { secret: refreshSecret, expiresIn: refreshExpiresIn }),
        ]);

        return {
            access_token: accessToken,
            refresh_token: refreshToken,
        };
    }

    /**
     * Connexion utilisateur
     */
    async login(dto: LoginDto) {
        const user = await this.usersService.findByEmailOrNull(dto.email);

        if (!user) {
            throw new UnauthorizedException('Identifiants invalides');
        }

        // Vérifier que le mot de passe existe (compte activé)
        if (!user.password) {
            throw new UnauthorizedException(
                'Compte non activé. Veuillez créer votre mot de passe via le lien reçu par email.',
            );
        }

        // Vérifier le mot de passe
        const isMatch = await bcrypt.compare(dto.password, user.password);
        if (!isMatch) {
            throw new UnauthorizedException('Identifiants invalides');
        }

        // Vérifier le statut du compte
        if (user.statut === StatutUtilisateurType.EN_ATTENTE) {
            throw new UnauthorizedException(
                'Votre compte est en attente de validation par un gestionnaire.',
            );
        }

        if (user.statut === StatutUtilisateurType.SUSPENDU) {
            throw new UnauthorizedException('Votre compte a été suspendu. Contactez un administrateur.');
        }

        return this.generateTokens(user.id, user.email, user.role);
    }

    /**
     * Rafraîchir les tokens
     */
    async refreshTokens(refreshToken: string) {
        try {
            const payload = await this.jwtService.verifyAsync(refreshToken, {
                secret: this.configService.get<string>('jwt.refreshSecret'),
            });

            const user = await this.usersService.findOne(payload.sub);
            if (!user) {
                throw new UnauthorizedException('Utilisateur introuvable');
            }

            return this.generateTokens(user.id, user.email, user.role);
        } catch {
            throw new UnauthorizedException('Refresh token invalide');
        }
    }

    /**
     * Inscription utilisateur OBSOLÈTE - Utiliser inscrireParentComplet() ou registerAM()
     * @deprecated
     */
    async register(registerDto: RegisterDto) {
        const exists = await this.usersService.findByEmailOrNull(registerDto.email);
        if (exists) {
            throw new ConflictException('Email déjà utilisé');
        }

        const allowedRoles = new Set<RoleType>([RoleType.PARENT, RoleType.ASSISTANTE_MATERNELLE]);
        if (!allowedRoles.has(registerDto.role)) {
            registerDto.role = RoleType.PARENT;
        }

        registerDto.statut = StatutUtilisateurType.EN_ATTENTE;

        if (!registerDto.consentement_photo) {
            registerDto.date_consentement_photo = null;
        } else if (registerDto.date_consentement_photo) {
            const date = new Date(registerDto.date_consentement_photo);
            if (isNaN(date.getTime())) {
                registerDto.date_consentement_photo = null;
            }
        }

        const user = await this.usersService.createUser(registerDto);
        const tokens = await this.generateTokens(user.id, user.email, user.role);

        return {
            ...tokens,
            user: {
                id: user.id,
                email: user.email,
                role: user.role,
                prenom: user.prenom,
                nom: user.nom,
                statut: user.statut,
            },
        };
    }

    /**
     * Inscription Parent COMPLÈTE - Workflow CDC 6 étapes en 1 transaction
     * Gère : Parent 1 + Parent 2 (opt) + Enfants + Présentation + CGU
     */
    async inscrireParentComplet(dto: RegisterParentCompletDto) {
        if (!dto.acceptation_cgu || !dto.acceptation_privacy) {
            throw new BadRequestException('L\'acceptation des CGU et de la politique de confidentialité est obligatoire');
        }

        if (!dto.enfants || dto.enfants.length === 0) {
            throw new BadRequestException('Au moins un enfant est requis');
        }

        const existe = await this.usersService.findByEmailOrNull(dto.email);
        if (existe) {
            throw new ConflictException('Un compte avec cet email existe déjà');
        }

        if (dto.co_parent_email) {
            const coParentExiste = await this.usersService.findByEmailOrNull(dto.co_parent_email);
            if (coParentExiste) {
                throw new ConflictException('L\'email du co-parent est déjà utilisé');
            }
        }

        const joursExpirationToken = await this.appConfigService.get<number>(
            'password_reset_token_expiry_days',
            7,
        );

        const tokenCreationMdp = crypto.randomUUID();
        const dateExpiration = new Date();
        dateExpiration.setDate(dateExpiration.getDate() + joursExpirationToken);

        const resultat = await this.usersRepo.manager.transaction(async (manager) => {
            const { numero: numeroDossier } = await this.numeroDossierService.getNextNumeroDossier(manager);

            const parent1 = manager.create(Users, {
                email: dto.email,
                prenom: dto.prenom,
                nom: dto.nom,
                role: RoleType.PARENT,
                statut: StatutUtilisateurType.EN_ATTENTE,
                telephone: dto.telephone,
                adresse: dto.adresse,
                code_postal: dto.code_postal,
                ville: dto.ville,
                token_creation_mdp: tokenCreationMdp,
                token_creation_mdp_expire_le: dateExpiration,
                numero_dossier: numeroDossier,
            });

            const parent1Enregistre = await manager.save(Users, parent1);

            let parent2Enregistre: Users | null = null;
            let tokenCoParent: string | null = null;

            if (dto.co_parent_email && dto.co_parent_prenom && dto.co_parent_nom) {
                tokenCoParent = crypto.randomUUID();
                const dateExpirationCoParent = new Date();
                dateExpirationCoParent.setDate(dateExpirationCoParent.getDate() + joursExpirationToken);

                const parent2 = manager.create(Users, {
                    email: dto.co_parent_email,
                    prenom: dto.co_parent_prenom,
                    nom: dto.co_parent_nom,
                    role: RoleType.PARENT,
                    statut: StatutUtilisateurType.EN_ATTENTE,
                    telephone: dto.co_parent_telephone,
                    adresse: dto.co_parent_meme_adresse ? dto.adresse : dto.co_parent_adresse,
                    code_postal: dto.co_parent_meme_adresse ? dto.code_postal : dto.co_parent_code_postal,
                    ville: dto.co_parent_meme_adresse ? dto.ville : dto.co_parent_ville,
                    token_creation_mdp: tokenCoParent,
                    token_creation_mdp_expire_le: dateExpirationCoParent,
                    numero_dossier: numeroDossier,
                });

                parent2Enregistre = await manager.save(Users, parent2);
            }

            const entiteParent = manager.create(Parents, {
                user_id: parent1Enregistre.id,
                numero_dossier: numeroDossier,
            });
            entiteParent.user = parent1Enregistre;
            if (parent2Enregistre) {
                entiteParent.co_parent = parent2Enregistre;
            }

            await manager.save(Parents, entiteParent);

            if (parent2Enregistre) {
                const entiteCoParent = manager.create(Parents, {
                    user_id: parent2Enregistre.id,
                    numero_dossier: numeroDossier,
                });
                entiteCoParent.user = parent2Enregistre;
                entiteCoParent.co_parent = parent1Enregistre;

                await manager.save(Parents, entiteCoParent);
            }

            const enfantsEnregistres: Children[] = [];
            for (const enfantDto of dto.enfants) {
                let urlPhoto: string | null = null;

                if (enfantDto.photo_base64 && enfantDto.photo_filename) {
                    urlPhoto = await this.sauvegarderPhotoDepuisBase64(
                        enfantDto.photo_base64,
                        enfantDto.photo_filename,
                    );
                }

                const enfant = new Children();
                enfant.first_name = enfantDto.prenom;
                enfant.last_name = enfantDto.nom || dto.nom;
                enfant.gender = enfantDto.genre;
                enfant.birth_date = enfantDto.date_naissance ? new Date(enfantDto.date_naissance) : undefined;
                enfant.due_date = enfantDto.date_previsionnelle_naissance 
                    ? new Date(enfantDto.date_previsionnelle_naissance) 
                    : undefined;
                enfant.photo_url = urlPhoto || undefined;
                enfant.status = enfantDto.date_naissance ? StatutEnfantType.ACTIF : StatutEnfantType.A_NAITRE;
                enfant.consent_photo = false;
                enfant.is_multiple = enfantDto.grossesse_multiple || false;

                const enfantEnregistre = await manager.save(Children, enfant);
                enfantsEnregistres.push(enfantEnregistre);

                const lienParentEnfant1 = manager.create(ParentsChildren, {
                    parentId: parent1Enregistre.id,
                    enfantId: enfantEnregistre.id,
                });
                await manager.save(ParentsChildren, lienParentEnfant1);

                if (parent2Enregistre) {
                    const lienParentEnfant2 = manager.create(ParentsChildren, {
                        parentId: parent2Enregistre.id,
                        enfantId: enfantEnregistre.id,
                    });
                    await manager.save(ParentsChildren, lienParentEnfant2);
                }
            }

            return {
                parent1: parent1Enregistre,
                parent2: parent2Enregistre,
                enfants: enfantsEnregistres,
                tokenCreationMdp,
                tokenCoParent,
            };
        });

        return {
            message: 'Inscription réussie. Votre dossier est en attente de validation par un gestionnaire.',
            parent_id: resultat.parent1.id,
            co_parent_id: resultat.parent2?.id,
            enfants_ids: resultat.enfants.map(e => e.id),
            statut: StatutUtilisateurType.EN_ATTENTE,
        };
    }

    /**
     * Inscription Assistante Maternelle COMPLÈTE - Un seul endpoint (identité + pro + photo + CGU)
     * Crée User (role AM) + entrée assistantes_maternelles, token création MDP
     */
    async inscrireAMComplet(dto: RegisterAMCompletDto) {
        if (!dto.acceptation_cgu || !dto.acceptation_privacy) {
            throw new BadRequestException(
                "L'acceptation des CGU et de la politique de confidentialité est obligatoire",
            );
        }

        const nirNormalized = (dto.nir || '').replace(/\s/g, '').toUpperCase();
        const nirValidation = validateNir(nirNormalized, {
            dateNaissance: dto.date_naissance,
        });
        if (!nirValidation.valid) {
            throw new BadRequestException(nirValidation.error || 'NIR invalide');
        }
        if (nirValidation.warning) {
            // Warning uniquement : on ne bloque pas (AM souvent étrangères, DOM-TOM, Corse)
            console.warn('[inscrireAMComplet] NIR warning:', nirValidation.warning, 'email=', dto.email);
        }

        const existe = await this.usersService.findByEmailOrNull(dto.email);
        if (existe) {
            throw new ConflictException('Un compte avec cet email existe déjà');
        }

        const joursExpirationToken = await this.appConfigService.get<number>(
            'password_reset_token_expiry_days',
            7,
        );
        const tokenCreationMdp = crypto.randomUUID();
        const dateExpiration = new Date();
        dateExpiration.setDate(dateExpiration.getDate() + joursExpirationToken);

        let urlPhoto: string | null = null;
        if (dto.photo_base64 && dto.photo_filename) {
            urlPhoto = await this.sauvegarderPhotoDepuisBase64(dto.photo_base64, dto.photo_filename);
        }

        const dateConsentementPhoto =
            dto.consentement_photo ? new Date() : undefined;

        const resultat = await this.usersRepo.manager.transaction(async (manager) => {
            const { numero: numeroDossier } = await this.numeroDossierService.getNextNumeroDossier(manager);

            const user = manager.create(Users, {
                email: dto.email,
                prenom: dto.prenom,
                nom: dto.nom,
                role: RoleType.ASSISTANTE_MATERNELLE,
                statut: StatutUtilisateurType.EN_ATTENTE,
                telephone: dto.telephone,
                adresse: dto.adresse,
                code_postal: dto.code_postal,
                ville: dto.ville,
                token_creation_mdp: tokenCreationMdp,
                token_creation_mdp_expire_le: dateExpiration,
                photo_url: urlPhoto ?? undefined,
                consentement_photo: dto.consentement_photo,
                date_consentement_photo: dateConsentementPhoto,
                date_naissance: dto.date_naissance ? new Date(dto.date_naissance) : undefined,
                numero_dossier: numeroDossier,
            });
            const userEnregistre = await manager.save(Users, user);

            const amRepo = manager.getRepository(AssistanteMaternelle);
            const am = amRepo.create({
                user_id: userEnregistre.id,
                approval_number: dto.numero_agrement,
                nir: nirNormalized,
                max_children: dto.capacite_accueil,
                biography: dto.biographie,
                residence_city: dto.ville ?? undefined,
                agreement_date: dto.date_agrement ? new Date(dto.date_agrement) : undefined,
                available: true,
                numero_dossier: numeroDossier,
            });
            await amRepo.save(am);

            return { user: userEnregistre };
        });

        return {
            message:
                'Inscription réussie. Votre dossier est en attente de validation par un gestionnaire.',
            user_id: resultat.user.id,
            statut: StatutUtilisateurType.EN_ATTENTE,
        };
    }

    /**
     * Sauvegarde une photo depuis base64 vers le système de fichiers
     */
    private async sauvegarderPhotoDepuisBase64(donneesBase64: string, nomFichier: string): Promise<string> {
        const correspondances = donneesBase64.match(/^data:image\/(\w+);base64,(.+)$/);
        if (!correspondances) {
            throw new BadRequestException('Format de photo invalide (doit être base64)');
        }

        const extension = correspondances[1];
        const tamponImage = Buffer.from(correspondances[2], 'base64');

        const dossierUpload = '/app/uploads/photos';
        await fs.mkdir(dossierUpload, { recursive: true });

        const nomFichierUnique = `${Date.now()}-${crypto.randomUUID()}.${extension}`;
        const cheminFichier = path.join(dossierUpload, nomFichierUnique);

        await fs.writeFile(cheminFichier, tamponImage);

        return `/uploads/photos/${nomFichierUnique}`;
    }

    /**
     * Changement de mot de passe obligatoire (première connexion)
     */
    async changePasswordRequired(
        userId: string,
        motDePasseActuel: string,
        nouveauMotDePasse: string,
    ) {
        const user = await this.usersRepo.findOne({ where: { id: userId } });

        if (!user) {
            throw new UnauthorizedException('Utilisateur introuvable');
        }

        // Vérifier que le changement est bien obligatoire
        if (!user.changement_mdp_obligatoire) {
            throw new BadRequestException(
                'Le changement de mot de passe n\'est pas requis pour cet utilisateur',
            );
        }

        // Vérifier que l'utilisateur a un mot de passe
        if (!user.password) {
            throw new BadRequestException('Compte non activé');
        }

        // Vérifier le mot de passe actuel
        const motDePasseValide = await bcrypt.compare(motDePasseActuel, user.password);
        if (!motDePasseValide) {
            throw new BadRequestException('Mot de passe actuel incorrect');
        }

        // Vérifier que le nouveau mot de passe est différent de l'ancien
        const memeMotDePasse = await bcrypt.compare(nouveauMotDePasse, user.password);
        if (memeMotDePasse) {
            throw new BadRequestException(
                'Le nouveau mot de passe doit être différent de l\'ancien',
            );
        }

        // Hasher et sauvegarder le nouveau mot de passe
        const sel = await bcrypt.genSalt(12);
        user.password = await bcrypt.hash(nouveauMotDePasse, sel);
        user.changement_mdp_obligatoire = false;
        user.modifie_le = new Date();

        await this.usersRepo.save(user);

        return {
            success: true,
            message: 'Mot de passe changé avec succès',
        };
    }

    async logout(userId: string) {
        return { success: true, message: 'Deconnexion'}
    }
}
