import { FastifyInstance, FastifyRequest } from 'fastify';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';

const prisma = new PrismaClient();

// Helper to extract client info from request
function extractClientInfo(request: FastifyRequest) {
    return {
        ipAddress: request.ip,
        userAgent: request.headers['user-agent'] || undefined,
        sessionId: (request as any).sessionId || undefined
    };
}

// Helper to create audit log entry
export async function createAuditLog(data: {
    userId?: string;
    userName?: string;
    action: 'CREATE' | 'UPDATE' | 'DELETE' | 'LOGIN' | 'LOGOUT' | 'ACCESS' | 'EXPORT' | 'APPROVE' | 'REJECT';
    entity: string;
    entityId?: string;
    entityName?: string;
    before?: any;
    after?: any;
    changes?: any;
    metadata?: any;
    ipAddress?: string;
    userAgent?: string;
    sessionId?: string;
}) {
    try {
        return await prisma.auditLog.create({ data });
    } catch (error) {
        console.error('Failed to create audit log:', error);
        return null;
    }
}

// Calculate changes between two objects
function calculateChanges(before: any, after: any): any {
    if (!before || !after) return null;

    const changes: any = {};
    const allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);

    for (const key of allKeys) {
        if (JSON.stringify(before[key]) !== JSON.stringify(after[key])) {
            changes[key] = { from: before[key], to: after[key] };
        }
    }

    return Object.keys(changes).length > 0 ? changes : null;
}

export async function auditRoutes(fastify: FastifyInstance) {
    // Auth middleware
    fastify.addHook('onRequest', async (request) => {
        try {
            await request.jwtVerify();
        } catch (err) {
            throw err;
        }
    });

    /**
     * GET /audit/logs
     * Query audit logs with filters
     */
    fastify.get('/logs', {
        handler: async (request, reply) => {
            try {
                const {
                    userId, entity, entityId, action,
                    startDate, endDate,
                    page = 1, limit = 50
                } = request.query as any;

                const where: any = {};

                if (userId) where.userId = userId;
                if (entity) where.entity = entity;
                if (entityId) where.entityId = entityId;
                if (action) where.action = action;
                if (startDate || endDate) {
                    where.timestamp = {};
                    if (startDate) where.timestamp.gte = new Date(startDate);
                    if (endDate) where.timestamp.lte = new Date(endDate);
                }

                const [logs, total] = await Promise.all([
                    prisma.auditLog.findMany({
                        where,
                        orderBy: { timestamp: 'desc' },
                        skip: (page - 1) * limit,
                        take: parseInt(limit)
                    }),
                    prisma.auditLog.count({ where })
                ]);

                return {
                    logs,
                    pagination: {
                        page: parseInt(page),
                        limit: parseInt(limit),
                        total,
                        pages: Math.ceil(total / limit)
                    }
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch audit logs' });
            }
        }
    });

    /**
     * GET /audit/logs/:id
     * Get single audit log entry with full details
     */
    fastify.get('/logs/:id', {
        handler: async (request, reply) => {
            try {
                const { id } = request.params as any;

                const log = await prisma.auditLog.findUnique({ where: { id } });
                if (!log) {
                    return reply.status(404).send({ error: 'Log not found' });
                }

                return log;
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch log' });
            }
        }
    });

    /**
     * GET /audit/entity/:entity/:entityId
     * Get all audit logs for a specific entity
     */
    fastify.get('/entity/:entity/:entityId', {
        handler: async (request, reply) => {
            try {
                const { entity, entityId } = request.params as any;

                const logs = await prisma.auditLog.findMany({
                    where: { entity, entityId },
                    orderBy: { timestamp: 'desc' }
                });

                return {
                    entity,
                    entityId,
                    history: logs
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch entity history' });
            }
        }
    });

    /**
     * GET /audit/user/:userId
     * Get all audit logs for a specific user
     */
    fastify.get('/user/:userId', {
        handler: async (request, reply) => {
            try {
                const { userId } = request.params as any;
                const { limit = 100 } = request.query as any;

                const logs = await prisma.auditLog.findMany({
                    where: { userId },
                    orderBy: { timestamp: 'desc' },
                    take: parseInt(limit)
                });

                return {
                    userId,
                    activityLog: logs
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch user activity' });
            }
        }
    });

    /**
     * GET /audit/summary
     * Get audit log summary/statistics
     */
    fastify.get('/summary', {
        handler: async (request, reply) => {
            try {
                const { startDate, endDate } = request.query as any;

                const where: any = {};
                if (startDate || endDate) {
                    where.timestamp = {};
                    if (startDate) where.timestamp.gte = new Date(startDate);
                    if (endDate) where.timestamp.lte = new Date(endDate);
                }

                const [totalLogs, actionCounts, entityCounts, recentActivity] = await Promise.all([
                    prisma.auditLog.count({ where }),
                    prisma.auditLog.groupBy({
                        by: ['action'],
                        where,
                        _count: true
                    }),
                    prisma.auditLog.groupBy({
                        by: ['entity'],
                        where,
                        _count: true,
                        orderBy: { _count: { entity: 'desc' } },
                        take: 10
                    }),
                    prisma.auditLog.findMany({
                        where,
                        orderBy: { timestamp: 'desc' },
                        take: 10,
                        select: {
                            id: true,
                            action: true,
                            entity: true,
                            entityName: true,
                            userName: true,
                            timestamp: true
                        }
                    })
                ]);

                return {
                    totalLogs,
                    byAction: actionCounts.reduce((acc: any, item) => {
                        acc[item.action] = item._count;
                        return acc;
                    }, {}),
                    byEntity: entityCounts.map(e => ({
                        entity: e.entity,
                        count: e._count
                    })),
                    recentActivity
                };
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to fetch summary' });
            }
        }
    });

    /**
     * GET /audit/export
     * Export audit logs as CSV
     */
    fastify.get('/export', {
        handler: async (request, reply) => {
            try {
                const user = request.user as any;
                const { entity, startDate, endDate } = request.query as any;

                const where: any = {};
                if (entity) where.entity = entity;
                if (startDate || endDate) {
                    where.timestamp = {};
                    if (startDate) where.timestamp.gte = new Date(startDate);
                    if (endDate) where.timestamp.lte = new Date(endDate);
                }

                const logs = await prisma.auditLog.findMany({
                    where,
                    orderBy: { timestamp: 'desc' },
                    take: 10000 // Limit export size
                });

                // Log the export action
                await createAuditLog({
                    userId: user.id,
                    userName: user.name || user.email,
                    action: 'EXPORT',
                    entity: 'AuditLog',
                    metadata: { count: logs.length, filters: { entity, startDate, endDate } },
                    ...extractClientInfo(request)
                });

                // Generate CSV
                const headers = ['Timestamp', 'User', 'Action', 'Entity', 'Entity Name', 'Entity ID', 'IP Address'];
                const rows = logs.map(log => [
                    log.timestamp.toISOString(),
                    log.userName || log.userId || 'System',
                    log.action,
                    log.entity,
                    log.entityName || '',
                    log.entityId || '',
                    log.ipAddress || ''
                ]);

                const csv = [
                    headers.join(','),
                    ...rows.map(r => r.map(c => `"${c}"`).join(','))
                ].join('\n');

                reply.header('Content-Type', 'text/csv');
                reply.header('Content-Disposition', `attachment; filename="audit-log-${new Date().toISOString().split('T')[0]}.csv"`);

                return csv;
            } catch (error) {
                fastify.log.error(error);
                return reply.status(500).send({ error: 'Failed to export' });
            }
        }
    });
}

// Export helper for use in other routes
export { createAuditLog as logAuditEvent, calculateChanges };
