import express from 'express';
import { ProfilerOptions, ProfilerStats, LoggingConfig, MetricsConfig } from './types';
import winston from 'winston';
import 'winston-daily-rotate-file';
import path from 'path';
import fs from 'fs';
import pidusage from 'pidusage';
import os from 'os';

export class BodhiProfiler {
    private options: ProfilerOptions;
    private logger: winston.Logger;
    private stats: ProfilerStats;
    private startTime: number;
    private lastCpuUsage = process.cpuUsage();
    private lastCpuCheck = Date.now();
    private apiResponseTimes: number[];
    private maxResponseTimes: number;

    constructor(options: ProfilerOptions = {}) {
        this.options = {
            serviceName: 'app',
            enableWebDashboard: true,
            port: 45678,
            logPath: './logs/profiler',
            sampleInterval: 5000,
            logging: {
                console: false,
                file: true,
                format: 'json',
                rotation: {
                    maxFiles: '7d',
                    maxSize: '5m',
                    datePattern: 'YYYY-MM-DD',
                    compress: true
                },
                cleanup: {
                    enabled: true,
                    maxAge: '30d'
                }
            },
            metrics: {
                cpu: {
                    enabled: true,
                    threshold: 70
                },
                memory: {
                    enabled: true,
                    threshold: 80
                },
                eventLoop: {
                    enabled: true,
                    threshold: 100
                },
                api: {
                    responseTime: {
                        enabled: true,
                        threshold: 1000
                    },
                    errorRate: true,
                    throughput: {
                        enabled: true,
                        interval: '1m'
                    }
                }
            },
            ...options
        };

        this.setupLogger();
        this.startTime = Date.now();
        this.stats = this.initializeStats();
        this.apiResponseTimes = [];
        this.maxResponseTimes = 100; // Keep last 100 response times
        this.startMetricsCollection();
        
        if (this.options.enableWebDashboard) {
            this.setupDashboard();
        }
    }

    private setupLogger() {
        const logDir = this.options.logPath!;
        const archiveDir = path.join(path.dirname(logDir), 'archive');

        // Ensure directories exist
        [logDir, archiveDir].forEach(dir => {
            if (!fs.existsSync(dir)) {
                fs.mkdirSync(dir, { recursive: true });
            }
        });

        const formats = [
            winston.format.timestamp(),
            winston.format.json()
        ];

        const transports: winston.transport[] = [];

        if (this.options.logging?.file) {
            transports.push(
                new winston.transports.DailyRotateFile({
                    dirname: logDir,
                    filename: 'profiler-%DATE%.log',
                    datePattern: this.options.logging.rotation?.datePattern || 'YYYY-MM-DD',
                    maxSize: this.options.logging.rotation?.maxSize || '5m',
                    maxFiles: this.options.logging.rotation?.maxFiles || '7d',
                    zippedArchive: this.options.logging.rotation?.compress || true
                })
            );
        }

        if (this.options.logging?.console) {
            transports.push(new winston.transports.Console());
        }

        this.logger = winston.createLogger({
            format: winston.format.combine(...formats),
            transports
        });
    }

    private shouldLogMetric(type: keyof MetricsConfig, value: number): boolean {
        const config = this.options.metrics?.[type] as any;
        if (!config?.enabled) return false;
        return value >= (config.threshold || 0);
    }

    private calculateCpuUsage(): number {
        const now = Date.now();
        const currentCpuUsage = process.cpuUsage();
        
        const userDiff = currentCpuUsage.user - this.lastCpuUsage.user;
        const systemDiff = currentCpuUsage.system - this.lastCpuUsage.system;
        const timeDiff = now - this.lastCpuCheck;
        
        // Convert to percentage (0-100)
        const cpuPercent = ((userDiff + systemDiff) / (timeDiff * 1000)) * 100;
        
        this.lastCpuUsage = currentCpuUsage;
        this.lastCpuCheck = now;
        
        return Math.min(100, Math.max(0, cpuPercent));
    }

    private async collectMetrics() {
        try {
            const heapStats = process.memoryUsage();
            const totalMem = os.totalmem();
            const cpuUsage = this.calculateCpuUsage();
            
            this.stats = {
                timestamp: Date.now(),
                cpu: {
                    usage: cpuUsage,
                    system: cpuUsage * 0.3,
                    user: cpuUsage * 0.7
                },
                memory: {
                    used: heapStats.heapUsed,
                    total: totalMem,
                    rss: heapStats.rss,
                    heapUsed: heapStats.heapUsed,
                    heapTotal: heapStats.heapTotal,
                    percentage: ((heapStats.heapUsed / totalMem) * 100).toFixed(1)
                },
                eventLoop: {
                    latency: this.getEventLoopLag(),
                    lag: this.getEventLoopLag()
                }
            };

            console.log('Current Stats:', {
                cpu: this.stats.cpu.usage.toFixed(1) + '%',
                memory: Math.round(this.stats.memory.heapUsed / (1024 * 1024)) + 'MB',
                eventLoop: Math.round(this.stats.eventLoop.latency) + 'ms'
            });

            // Only log if thresholds are exceeded
            if (this.shouldLogMetric('cpu', this.stats.cpu.usage) ||
                this.shouldLogMetric('memory', (this.stats.memory.used / this.stats.memory.total) * 100) ||
                this.shouldLogMetric('eventLoop', this.stats.eventLoop.latency)) {
                this.logger.info('metrics', this.stats);
            }

        } catch (error) {
            console.error('Error collecting metrics:', error);
            this.logger.error('Error collecting metrics:', error);
        }
    }

    public cleanupLogs() {
        if (!this.options.logging?.cleanup?.enabled) return;

        const archiveDir = path.join(path.dirname(this.options.logPath!), 'archive');
        if (!fs.existsSync(archiveDir)) return;

        const maxAge = this.options.logging.cleanup.maxAge || '30d';
        const maxAgeMs = this.parseTimeString(maxAge);
        const now = Date.now();

        fs.readdirSync(archiveDir)
            .forEach(file => {
                const filePath = path.join(archiveDir, file);
                const stats = fs.statSync(filePath);
                if (now - stats.mtimeMs > maxAgeMs) {
                    fs.unlinkSync(filePath);
                }
            });
    }

    private parseTimeString(time: string): number {
        const unit = time.slice(-1);
        const value = parseInt(time.slice(0, -1));
        switch (unit) {
            case 'd': return value * 24 * 60 * 60 * 1000;
            case 'h': return value * 60 * 60 * 1000;
            case 'm': return value * 60 * 1000;
            case 's': return value * 1000;
            default: return value;
        }
    }

    private initializeStats(): ProfilerStats {
        return {
            timestamp: Date.now(),
            cpu: {
                usage: 0,
                system: 0,
                user: 0
            },
            memory: {
                used: 0,
                total: 0,
                rss: 0,
                heapUsed: 0,
                heapTotal: 0
            },
            eventLoop: {
                latency: 0,
                lag: 0
            }
        };
    }

    private startMetricsCollection() {
        // Initial collection
        this.collectMetrics();
        
        // Regular collection every second
        setInterval(() => {
            this.collectMetrics();
        }, 1000);
    }

    private setupDashboard() {
        const app = express();
        const dashboardPath = path.join(__dirname, '..', 'src', 'dashboard');
        
        // Serve static dashboard files
        app.use('/profiler', express.static(dashboardPath));

        // Stats endpoint
        app.get('/profiler/stats', async (req: any, res: any) => {
            try {
                const systemStats = this.stats;
                const avgResponseTime = this.apiResponseTimes.length > 0
                    ? this.apiResponseTimes.reduce((a, b) => a + b, 0) / this.apiResponseTimes.length
                    : 0;

                const uptime = process.uptime();
                const uptimeStr = uptime.toFixed(0);

                res.json({
                    cpu: {
                        percentage: parseFloat(systemStats.cpu.usage.toFixed(1)),
                        system: systemStats.cpu.system,
                        user: systemStats.cpu.user
                    },
                    memory: {
                        heapUsed: Math.round(systemStats.memory.heapUsed / 1024 / 1024),
                        heapTotal: Math.round(systemStats.memory.heapTotal / 1024 / 1024),
                        percentage: Math.round((systemStats.memory.heapUsed / systemStats.memory.heapTotal) * 100),
                        rss: Math.round(systemStats.memory.rss / 1024 / 1024)
                    },
                    eventLoop: {
                        latency: systemStats.eventLoop.latency
                    },
                    api: {
                        responseTime: avgResponseTime
                    },
                    uptime: uptimeStr,
                    timestamp: Date.now()
                });
            } catch (error) {
                console.error('Error getting stats:', error);
                res.status(500).json({ error: 'Failed to get stats' });
            }
        });

        app.listen(this.options.port, () => {
            console.log(`Profiler dashboard available at http://localhost:${this.options.port}/profiler`);
        });
    }

    private getEventLoopLag(): number {
        const start = process.hrtime();
        const NS_PER_MS = 1e6;
        return process.hrtime(start)[1] / NS_PER_MS;
    }

    public middleware() {
        return (req: any, res: any, next: any) => {
            const start = process.hrtime();

            res.on('finish', () => {
                const [seconds, nanoseconds] = process.hrtime(start);
                const duration = seconds * 1000 + nanoseconds / 1000000; // Convert to milliseconds
                this.apiResponseTimes.push(duration);
                
                // Keep only the last maxResponseTimes entries
                if (this.apiResponseTimes.length > this.maxResponseTimes) {
                    this.apiResponseTimes.shift();
                }
            });

            next();
        };
    }
}

export * from './types';
