import { Injectable, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ModelsService } from 'src/models/models.service';
import { CommonService } from 'src/common/common.service';
import { TranslationService } from 'src/common/services/translation.service';
import { EmailService } from 'src/common/services/email.service';
import { ActivityService } from 'src/common/services/activity.service';
import { Types } from 'mongoose';
import { PANEL_ROLES, STAFF_ROLES, UserType } from 'src/user/schema/users.schema';
import * as bcrypt from 'bcrypt';
import { AdminLoginDto, AdminForgotPasswordDto, AdminResetPasswordDto, AdminAcceptInviteDto, AdminChangePasswordDto, UpdateAdminProfileDto } from './dto/admin.dto';

const RESET_TOKEN_EXPIRY_MINUTES = 30;

@Injectable()
export class AdminService {
    private readonly logger = new Logger(AdminService.name);

    constructor(
        private readonly models: ModelsService,
        private readonly common: CommonService,
        private readonly emailService: EmailService,
        private readonly activity: ActivityService,
        private readonly translationService: TranslationService,
        private readonly configService: ConfigService
    ) { }

    private t(key: string, lang: string, params?: Record<string, string>) {
        return this.translationService.translate(key, lang, params);
    }

    private findPanelUser(filter: Record<string, any>, projection?: string) {
        return this.models.UserModel.findOne(
            { ...filter, user_type: { $in: PANEL_ROLES }, is_deleted: { $ne: true } },
            projection
        );
    }

    async login(dto: AdminLoginDto, lang: string): Promise<any> {
        const { password } = dto;
        const user = await this.findPanelUser({ email: dto.email.trim().toLowerCase() }, '+password');

        if (!user || !user.password || !(await bcrypt.compare(password, user.password))) {
            throw new HttpException({ message: this.t('INVALID_CREDENTIALS', lang) }, HttpStatus.BAD_REQUEST);
        }

        if (!user.is_email_verified) {
            throw new HttpException({ message: this.t('EMAIL_NOT_VERIFIED', lang) }, HttpStatus.BAD_REQUEST);
        }

        if (!user.is_active) {
            throw new HttpException({ message: this.t('ACCOUNT_DEACTIVATED', lang) }, HttpStatus.FORBIDDEN);
        }

        const tokenPayload = {
            _id: user._id,
            scope: 'USER',
            user_type: user.user_type,
            token_gen_at: +new Date()
        };
        const accessToken = await this.common.generate_token(tokenPayload);
        await this.common.create_user_session(
            { email: user.email, user_type: user.user_type, device_type: 'WEB' },
            tokenPayload,
            accessToken
        );

        return this.common.successResponse(this.t('LOGIN_SUCCESS', lang), {
            token: accessToken,
            user: {
                _id: user._id,
                name: user.name,
                email: user.email,
                profile_pic: user.profile_pic,
                user_type: user.user_type,
                is_email_verified: user.is_email_verified,
                created_at: user.created_at,
                updated_at: user.updated_at
            }
        });
    }

    /** Emails a single-use reset link. Always responds the same so accounts can't be enumerated. */
    async forgotPassword(dto: AdminForgotPasswordDto, lang: string): Promise<any> {
        const user = await this.findPanelUser({ email: dto.email.trim().toLowerCase(), is_email_verified: true, is_active: true });

        if (user) {
            const token = this.common.generateSecureToken();
            await this.models.UserModel.updateOne(
                { _id: user._id },
                {
                    $set: {
                        password_reset_token: this.common.hashToken(token),
                        password_reset_expires_at: +new Date() + RESET_TOKEN_EXPIRY_MINUTES * 60 * 1000
                    }
                }
            );

            const baseUrl = (this.configService.get<string>('ADMIN_PANEL_URL') || '').replace(/\/$/, '');
            const resetUrl = `${baseUrl}/reset-password?token=${token}`;

            try {
                await this.emailService.sendForgotPasswordEmail(
                    user.email,
                    user.name,
                    resetUrl,
                    RESET_TOKEN_EXPIRY_MINUTES,
                    user.language || lang
                );
            } catch (error) {
                this.logger.error(`Failed to send reset link to ${user.email}`, error.stack);
            }
        }

        return this.common.successResponse(this.t('RESET_LINK_SENT', lang));
    }

    async resetPassword(dto: AdminResetPasswordDto, lang: string): Promise<any> {
        const user = await this.findPanelUser({
            password_reset_token: this.common.hashToken(dto.token),
            password_reset_expires_at: { $gt: +new Date() },
            is_email_verified: true
        });

        if (!user) {
            throw new HttpException({ message: this.t('INVALID_RESET_TOKEN', lang) }, HttpStatus.BAD_REQUEST);
        }

        await this.models.UserModel.updateOne(
            { _id: user._id },
            {
                $set: {
                    password: await bcrypt.hash(dto.password, 10),
                    password_reset_token: null,
                    password_reset_expires_at: null,
                    updated_at: +new Date()
                }
            }
        );
        await this.models.SessionModel.deleteMany({ user_id: user._id });

        return this.common.successResponse(this.t('PASSWORD_CHANGED_SUCCESS', lang));
    }

    /** Invited staff set their first password from the emailed link. */
    async acceptInvite(dto: AdminAcceptInviteDto, lang: string): Promise<any> {
        const user = await this.findPanelUser({
            password_reset_token: this.common.hashToken(dto.token),
            password_reset_expires_at: { $gt: +new Date() },
            is_email_verified: false
        });

        if (!user) {
            throw new HttpException({ message: this.t('INVALID_INVITE_TOKEN', lang) }, HttpStatus.BAD_REQUEST);
        }

        await this.models.UserModel.updateOne(
            { _id: user._id },
            {
                $set: {
                    password: await bcrypt.hash(dto.password, 10),
                    is_email_verified: true,
                    password_reset_token: null,
                    password_reset_expires_at: null,
                    updated_at: +new Date()
                }
            }
        );
        await this.activity.log(user._id, 'INVITE_ACCEPTED', user);

        return this.common.successResponse(this.t('INVITE_ACCEPTED', lang));
    }

    async changePassword(adminId: string, dto: AdminChangePasswordDto, lang: string): Promise<any> {
        const user = await this.findPanelUser({ _id: new Types.ObjectId(adminId) }, '+password');

        if (!user) {
            throw new HttpException({ message: this.t('USER_NOT_FOUND', lang) }, HttpStatus.NOT_FOUND);
        }

        if (!(await bcrypt.compare(dto.old_password, user.password))) {
            throw new HttpException({ message: this.t('CURRENT_PASSWORD_INCORRECT', lang) }, HttpStatus.UNAUTHORIZED);
        }

        if (await bcrypt.compare(dto.new_password, user.password)) {
            throw new HttpException({ message: this.t('NEW_PASSWORD_SAME', lang) }, HttpStatus.BAD_REQUEST);
        }

        await this.models.UserModel.updateOne(
            { _id: user._id },
            { $set: { password: await bcrypt.hash(dto.new_password, 10), updated_at: +new Date() } }
        );

        return this.common.successResponse(this.t('PASSWORD_CHANGED', lang));
    }

    async logout(adminId: string, lang: string): Promise<any> {
        await this.models.SessionModel.deleteMany({ user_id: new Types.ObjectId(adminId) });
        return this.common.successResponse(this.t('LOGOUT_SUCCESS', lang));
    }

    async getProfile(adminId: string, lang: string): Promise<any> {
        const user = await this.findPanelUser({ _id: new Types.ObjectId(adminId) })
            .select('-password -__v -socket_id');

        if (!user) {
            throw new HttpException({ message: this.t('USER_NOT_FOUND', lang) }, HttpStatus.NOT_FOUND);
        }

        return this.common.successResponse(this.t('PROFILE_RETRIEVED', lang), user);
    }

    async updateProfile(adminId: string, dto: UpdateAdminProfileDto, lang: string): Promise<any> {
        const user = await this.findPanelUser({ _id: new Types.ObjectId(adminId) });

        if (!user) {
            throw new HttpException({ message: this.t('USER_NOT_FOUND', lang) }, HttpStatus.NOT_FOUND);
        }

        const updateData: any = { updated_at: +new Date() };
        if (dto.name !== undefined) updateData.name = dto.name;
        if (dto.profile_pic !== undefined) updateData.profile_pic = dto.profile_pic;

        if (dto.email !== undefined) {
            const existing = await this.models.UserModel.findOne({ email: dto.email, _id: { $ne: user._id } });
            if (existing) {
                throw new HttpException({ message: this.t('EMAIL_ALREADY_EXISTS', lang) }, HttpStatus.BAD_REQUEST);
            }
            updateData.email = dto.email;
        }

        await this.models.UserModel.updateOne({ _id: user._id }, { $set: updateData });
        const updatedUser = await this.models.UserModel.findById(user._id)
            .select('-password -__v -socket_id');

        return this.common.successResponse(this.t('PROFILE_UPDATED', lang), updatedUser);
    }
}
