import { FastifyInstance } from 'fastify';
import { buildApp } from '../../../app';
import { UserService } from '../services/UserService';
import { UserRoleType } from '@/constants/roles';
import jwt from 'jsonwebtoken';
import { describe, beforeAll, afterAll, it, expect } from '@jest/globals';
import { MikroORM, EntityManager, IDatabaseDriver, Connection } from '@mikro-orm/core';

// Extend FastifyInstance to include container
declare module 'fastify' {
    interface FastifyInstance {
        container: {
            resolve: (name: string) => any;
        };
    }
}

describe('Authentication System Tests', () => {
    let appInstance: FastifyInstance;
    let userService: UserService;
    let testUserToken: string;
    let testAdminToken: string;
    let orm: EntityManager<IDatabaseDriver<Connection>>;

    const testUser = {
        username: 'testuser',
        password: 'TestPass123!',
        email: 'test@example.com',
        roleName: 'user' as UserRoleType
    };

    const testAdmin = {
        username: 'testadmin',
        password: 'AdminPass123!',
        email: 'admin@example.com',
        roleName: 'admin' as UserRoleType
    };

    beforeAll(async () => {
        appInstance = await buildApp();
        userService = appInstance.container.resolve('userService');
        orm = appInstance.orm.em;

        // Create test users
        await userService.createUserWithRole(
            testUser.username,
            testUser.password,
            testUser.email,
            testUser.roleName
        );

        await userService.createUserWithRole(
            testAdmin.username,
            testAdmin.password,
            testAdmin.email,
            testAdmin.roleName
        );
    });

    afterAll(async () => {
        // Clean up test users using the entity manager
        const em = orm.fork();
        const user = await em.findOne('User', { username: testUser.username });
        const admin = await em.findOne('User', { username: testAdmin.username });

        if (user) {
            await em.removeAndFlush(user);
        }

        if (admin) {
            await em.removeAndFlush(admin);
        }

        await appInstance.close();
    });

    describe('Login Endpoint Tests', () => {
        it('should successfully login a regular user', async () => {
            const response = await appInstance.inject({
                method: 'POST',
                url: '/api/auth/login',
                payload: {
                    username: testUser.username,
                    password: testUser.password
                }
            });

            expect(response.statusCode).toBe(200);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(true);
            expect(data.message).toBe('Authentication successful');
            expect(data.data.token).toBeDefined();
            expect(data.data.user).toBeDefined();
            expect(data.data.user.username).toBe(testUser.username);
            expect(data.data.user.roles).toContain('user');

            testUserToken = data.data.token;
        });

        it('should successfully login an admin user', async () => {
            const response = await appInstance.inject({
                method: 'POST',
                url: '/api/auth/login',
                payload: {
                    username: testAdmin.username,
                    password: testAdmin.password
                }
            });

            expect(response.statusCode).toBe(200);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(true);
            expect(data.data.user.roles).toContain('admin');

            testAdminToken = data.data.token;
        });

        it('should fail login with incorrect password', async () => {
            const response = await appInstance.inject({
                method: 'POST',
                url: '/api/auth/login',
                payload: {
                    username: testUser.username,
                    password: 'wrongpassword'
                }
            });

            expect(response.statusCode).toBe(401);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(false);
            expect(data.message).toBe('Invalid username or password');
        });

        it('should fail login with non-existent username', async () => {
            const response = await appInstance.inject({
                method: 'POST',
                url: '/api/auth/login',
                payload: {
                    username: 'nonexistentuser',
                    password: 'anypassword'
                }
            });

            expect(response.statusCode).toBe(401);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(false);
            expect(data.message).toBe('Invalid username or password');
        });

        it('should fail login with missing credentials', async () => {
            const response = await appInstance.inject({
                method: 'POST',
                url: '/api/auth/login',
                payload: {}
            });

            expect(response.statusCode).toBe(400);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(false);
        });
    });

    describe('Token Validation Tests', () => {
        it('should validate a valid token', async () => {
            const response = await appInstance.inject({
                method: 'GET',
                url: '/api/auth/validate',
                headers: {
                    Authorization: `Bearer ${testUserToken}`
                }
            });

            expect(response.statusCode).toBe(200);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(true);
            expect(data.data.valid).toBe(true);
        });

        it('should reject an invalid token', async () => {
            const response = await appInstance.inject({
                method: 'GET',
                url: '/api/auth/validate',
                headers: {
                    Authorization: 'Bearer invalid.token.here'
                }
            });

            expect(response.statusCode).toBe(401);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(false);
        });

        it('should reject an expired token', async () => {
            // Create an expired token
            const expiredToken = jwt.sign(
                { id: 1, username: testUser.username, roles: ['user'] },
                process.env.JWT_SECRET as string,
                { expiresIn: '-1h' }
            );

            const response = await appInstance.inject({
                method: 'GET',
                url: '/api/auth/validate',
                headers: {
                    Authorization: `Bearer ${expiredToken}`
                }
            });

            expect(response.statusCode).toBe(401);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(false);
        });

        it('should reject a request without token', async () => {
            const response = await appInstance.inject({
                method: 'GET',
                url: '/api/auth/validate'
            });

            expect(response.statusCode).toBe(401);
            const data = JSON.parse(response.payload);
            expect(data.success).toBe(false);
        });
    });

    describe('Role-Based Access Tests', () => {
        it('should allow admin to access admin-only endpoint', async () => {
            const response = await appInstance.inject({
                method: 'GET',
                url: '/api/admin/dashboard',
                headers: {
                    Authorization: `Bearer ${testAdminToken}`
                }
            });

            expect(response.statusCode).toBe(200);
        });

        it('should deny regular user access to admin-only endpoint', async () => {
            const response = await appInstance.inject({
                method: 'GET',
                url: '/api/admin/dashboard',
                headers: {
                    Authorization: `Bearer ${testUserToken}`
                }
            });

            expect(response.statusCode).toBe(403);
        });
    });
}); 