import { prisma } from '../config/database';
import { Prisma } from '@prisma/client';
import { config } from '../config';
import { logger } from '../config/logger';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import {
    LoginRequest,
    RegisterRequest,
    LoginResponse,
    RegisterResponse,
    RefreshTokenResponse,
    ResetPasswordResponse,
    ResetRequestResponse
} from '../types/auth.types';
import { UserResponse } from '../types/user.types';
import { UnauthorizedError, ValidationError, ForbiddenError } from '../middleware/errorHandler';
import nodemailer from 'nodemailer';
import { getPresignedUrl } from '../config/minio';

export class AuthService {
    /**
     * User login
     */
    async login(data: LoginRequest): Promise<LoginResponse> {
        const { email, password } = data;

        // Find user with role and profile
        const user = await prisma.user.findUnique({
            where: { email },
            include: {
                role: true,
                profile: true
            }
        });
        if (!user) {
            logger.warn('Login attempt with invalid email', { email });
            throw new UnauthorizedError('Invalid email or password');
        }

        // Check if account is deactivated
        if (user.is_deactivated) {
            logger.warn('Login attempt with deactivated account', { email, userId: user.id });
            throw new ForbiddenError('This account has been deactivated');
        }

        // Verify password
        const isPasswordValid = await bcrypt.compare(password, user.password);
        if (!isPasswordValid) {
            logger.warn('Login attempt with invalid password', { email });
            throw new UnauthorizedError('Invalid email or password');
        }

        // Generate tokens
        const token = this.generateAccessToken(user.id, user.email, user.email, user.role.code);
        const refreshToken = this.generateRefreshToken(user.id);

        logger.info('User logged in successfully', { userId: user.id, email: user.email });

        return {
            code: 200,
            message: 'Login successful',
            data: {
                token,
                refreshToken,
                user: await this.formatUserResponse(user)
            }
        };
    }

    /**
     * User registration
     */
    async register(data: RegisterRequest): Promise<RegisterResponse> {
        const { username, email, password, first_name, last_name, prefix, phone } = data;

        // Check if username already exists
        const existingUsername = await prisma.user.findUnique({
            where: { username }
        });

        if (existingUsername) {
            throw new ValidationError('Username already exists');
        }

        // Check if email already exists
        const existingEmail = await prisma.user.findUnique({
            where: { email }
        });

        if (existingEmail) {
            throw new ValidationError('Email already exists');
        }

        // Check if phone number already exists in user profiles
        const existingPhone = await prisma.userProfile.findFirst({
            where: { phone }
        });

        if (existingPhone) {
            throw new ValidationError('Phone number already exists');
        }

        // Get STUDENT role
        const studentRole = await prisma.role.findUnique({
            where: { code: 'STUDENT' }
        });

        if (!studentRole) {
            logger.error('STUDENT role not found in database');
            throw new Error('System configuration error');
        }

        // Hash password
        const hashedPassword = await bcrypt.hash(password, 10);

        // Create user with profile
        const user = await prisma.user.create({
            data: {
                username,
                email,
                password: hashedPassword,
                role_id: studentRole.id,
                profile: {
                    create: {
                        prefix: prefix ? prefix as Prisma.InputJsonValue : Prisma.JsonNull,
                        first_name,
                        last_name,
                        phone
                    }
                }
            },
            include: {
                role: true,
                profile: true
            }
        });

        logger.info('New user registered', { userId: user.id, username: user.username });

        return {
            user: this.formatUserResponseSync(user),
            message: 'Registration successful'
        };
    }

    async registerInstructor(data: RegisterRequest): Promise<RegisterResponse> {
        const { username, email, password, first_name, last_name, prefix, phone } = data;

        // Check if username already exists
        const existingUsername = await prisma.user.findUnique({
            where: { username }
        });

        if (existingUsername) {
            throw new ValidationError('Username already exists');
        }

        // Check if email already exists
        const existingEmail = await prisma.user.findUnique({
            where: { email }
        });

        if (existingEmail) {
            throw new ValidationError('Email already exists');
        }

        // Check if phone number already exists in user profiles
        const existingPhone = await prisma.userProfile.findFirst({
            where: { phone }
        });

        if (existingPhone) {
            throw new ValidationError('Phone number already exists');
        }

        // Get INSTRUCTOR role
        const instructorRole = await prisma.role.findUnique({
            where: { code: 'INSTRUCTOR' }
        });

        if (!instructorRole) {
            logger.error('INSTRUCTOR role not found in database');
            throw new Error('System configuration error');
        }

        // Hash password
        const hashedPassword = await bcrypt.hash(password, 10);

        // Create user with profile
        const user = await prisma.user.create({
            data: {
                username,
                email,
                password: hashedPassword,
                role_id: instructorRole.id,
                profile: {
                    create: {
                        prefix: prefix ? prefix as Prisma.InputJsonValue : Prisma.JsonNull,
                        first_name,
                        last_name,
                        phone
                    }
                }
            },
            include: {
                role: true,
                profile: true
            }
        });

        logger.info('New user registered', { userId: user.id, username: user.username });

        return {
            user: this.formatUserResponseSync(user),
            message: 'Registration successful'
        };
    }

    /**
     * Refresh access token
     */
    async refreshToken(refreshToken: string): Promise<RefreshTokenResponse> {
        try {
            // Verify refresh token
            const decoded = jwt.verify(refreshToken, config.jwt.secret) as { id: number; type: string };

            if (decoded.type !== 'refresh') {
                throw new UnauthorizedError('Invalid token type');
            }

            // Get user
            const user = await prisma.user.findUnique({
                where: { id: decoded.id },
                include: { role: true }
            });

            if (!user) {
                throw new UnauthorizedError('User not found');
            }

            // Generate new tokens
            const newToken = this.generateAccessToken(user.id, user.username, user.email, user.role.code);
            const newRefreshToken = this.generateRefreshToken(user.id);

            logger.info('Token refreshed', { userId: user.id });

            return {
                token: newToken,
                refreshToken: newRefreshToken
            };
        } catch (error) {
            if (error instanceof jwt.JsonWebTokenError) {
                throw new UnauthorizedError('Invalid refresh token');
            }
            if (error instanceof jwt.TokenExpiredError) {
                throw new UnauthorizedError('Refresh token expired');
            }
            throw error;
        }
    }

    /**
     * Reset request
     */
    async resetRequest(email: string): Promise<ResetRequestResponse> {
        try {
            // Find user with role
            const user = await prisma.user.findUnique({ 
                where: { email },
                include: { role: true }
            });
            if (!user) throw new UnauthorizedError('User not found');

            const token = jwt.sign({ id: user.id, email: user.email }, config.jwt.secret, { expiresIn: '1h' });

            // Create reset URL based on role
            const isInstructor = user.role.code === 'INSTRUCTOR';
            const baseUrl = isInstructor 
                ? (process.env.FRONTEND_URL_INSTRUCTOR || 'http://localhost:3001')
                : (process.env.FRONTEND_URL_STUDENT || 'http://localhost:3000');
            const resetURL = `${baseUrl}/reset-password?token=${token}`;

            // Create transporter
            const transporter = nodemailer.createTransport({
                host: config.smtp.host,
                port: config.smtp.port,
                secure: config.smtp.secure,
                ...(config.smtp.user && config.smtp.pass && {
                    auth: {
                        user: config.smtp.user,
                        pass: config.smtp.pass
                    }
                })
            });

            // Send email
            const mailOptions = {
                from: config.smtp.from,
                to: user.email,
                subject: 'Password Reset Request',
                text: `You are receiving this because you (or someone else) have requested the reset of the password for your account.\n\nPlease click on the following link, or paste this into your browser to complete the process:\n\n${resetURL}\n\nIf you did not request this, please ignore this email and your password will remain unchanged.\n`,
                html: `
                    <h2>Password Reset Request</h2>
                    <p>You are receiving this because you (or someone else) have requested the reset of the password for your account.</p>
                    <p>Please click on the following link to complete the process:</p>
                    <p><a href="${resetURL}">${resetURL}</a></p>
                    <p>If you did not request this, please ignore this email and your password will remain unchanged.</p>
                `
            };

            await transporter.sendMail(mailOptions);

            logger.info('Password reset email sent', { email: user.email });
            return {
                code: 200,
                message: 'Password reset email sent successfully'
            };
        } catch (error) {
            logger.error('Failed to send password reset email', { email, error });
            throw error;
        }
    }

    async resetPassword(token: string, password: string): Promise<ResetPasswordResponse> {
        try {
            const decoded = jwt.verify(token, config.jwt.secret) as { id: number; email: string };
            const user = await prisma.user.findUnique({ where: { id: decoded.id } });
            if (!user) throw new UnauthorizedError('User not found');

            const secret = config.jwt.secret;

            const verify = jwt.verify(token, secret);
            const encryptedPassword = await bcrypt.hash(password, 10);

            if (!verify) throw new UnauthorizedError('Invalid token');

            await prisma.user.update({
                where: { id: user.id },
                data: { password: encryptedPassword }
            });

            logger.info('Password reset successfully', { userId: user.id });
            return {
                code: 200,
                message: 'Password reset successfully'
            };
        } catch (error) {
            logger.error('Failed to reset password', { error });
            throw error;
        }
    }

    /**
     * Generate access token (JWT)
     */
    private generateAccessToken(id: number, username: string, email: string, roleCode: string): string {
        return jwt.sign(
            {
                id,
                username,
                email,
                roleCode
            },
            config.jwt.secret,
            { expiresIn: config.jwt.expiresIn } as jwt.SignOptions
        );
    }

    /**
     * Generate refresh token
     */
    private generateRefreshToken(id: number): string {
        return jwt.sign(
            {
                id,
                type: 'refresh'
            },
            config.jwt.secret,
            { expiresIn: config.jwt.refreshExpiresIn } as jwt.SignOptions
        );
    }

    /**
     * Format user response with presigned URL for avatar (for login)
     */
    private async formatUserResponse(user: any): Promise<UserResponse> {
        let avatar_url: string | null = null;
        if (user.profile?.avatar_url) {
            try {
                avatar_url = await getPresignedUrl(user.profile.avatar_url, 3600);
            } catch (err) {
                logger.warn(`Failed to generate presigned URL for avatar: ${err}`);
            }
        }

        return {
            id: user.id,
            username: user.username,
            email: user.email,
            email_verified_at: user.email_verified_at,
            updated_at: user.updated_at,
            created_at: user.created_at,
            role: {
                code: user.role.code,
                name: user.role.name
            },
            profile: user.profile ? {
                prefix: user.profile.prefix,
                first_name: user.profile.first_name,
                last_name: user.profile.last_name,
                phone: user.profile.phone,
                avatar_url: avatar_url,
                birth_date: user.profile.birth_date
            } : undefined
        };
    }

    /**
     * Format user response without presigned URL (for register)
     */
    private formatUserResponseSync(user: any): UserResponse {
        return {
            id: user.id,
            username: user.username,
            email: user.email,
            email_verified_at: user.email_verified_at,
            updated_at: user.updated_at,
            created_at: user.created_at,
            role: {
                code: user.role.code,
                name: user.role.name
            },
            profile: user.profile ? {
                prefix: user.profile.prefix,
                first_name: user.profile.first_name,
                last_name: user.profile.last_name,
                phone: user.profile.phone,
                avatar_url: user.profile.avatar_url,
                birth_date: user.profile.birth_date
            } : undefined
        };
    }
}
