import { spawn, ChildProcess } from 'child_process'
import path from 'path'
import { app } from 'electron'
import fs from 'fs'
import { configService } from './ConfigService'

export class ProcessManager {
    private processes: Map<string, ChildProcess> = new Map()
    private logs: Map<string, string[]> = new Map()
    private logDir: string

    constructor() {
        this.processes = new Map()
        this.logDir = ''
    }

    private initLogs() {
        if (this.logDir) return
        try {
            this.logDir = path.join(configService.getBasePath(), 'logs')
            if (!fs.existsSync(this.logDir)) {
                fs.mkdirSync(this.logDir, { recursive: true })
            }
            // Initialize with default logs
            ['nginx', 'apache', 'php', 'mariadb'].forEach(s => {
                if (!this.logs.has(s)) {
                    this.logs.set(s, [`[${new Date().toLocaleTimeString()}] Sistem hazır. İşlem bekleniyor...`])
                }
            })
        } catch (e) {
            console.error('Failed to init logs:', e)
        }
    }

    private addLog(name: string, data: string) {
        this.initLogs()
        if (!this.logs.has(name)) this.logs.set(name, [])
        const serviceLogs = this.logs.get(name)!
        const lines = data.split(/\r?\n/).filter(l => l.trim())
        const timestamp = new Date().toLocaleTimeString()

        lines.forEach(line => {
            const formattedLine = `[${timestamp}] ${line}`
            console.log(`[LOG:${name}] ${formattedLine}`)
            serviceLogs.push(formattedLine)
            // Persist to file
            if (this.logDir) {
                try {
                    fs.appendFileSync(path.join(this.logDir, `${name}.log`), formattedLine + '\n')
                } catch (e) {
                    // Ignore write errors to file if it happens
                }
            }
        })

        // Keep last 100 lines in memory
        if (serviceLogs.length > 100) {
            this.logs.set(name, serviceLogs.slice(-100))
        }
    }

    getLogs(name: string): string[] {
        this.initLogs()
        return this.logs.get(name) || []
    }

    async runOnce(binPath: string, args: string[], name?: string): Promise<{ success: boolean; output: string }> {
        return new Promise((resolve) => {
            const child = spawn(binPath, args, { stdio: 'pipe', shell: false, windowsHide: true })
            let output = ''

            child.stdout?.on('data', (data) => {
                const out = data.toString()
                output += out
                console.log(`[INIT:${name || '?'}] ${out}`)
                if (name) this.addLog(name, out)
            })

            child.stderr?.on('data', (data) => {
                const err = data.toString()
                output += err
                console.error(`[INIT ERROR:${name || '?'}] ${err}`)
                if (name) this.addLog(name, err)
            })

            child.on('error', (err) => {
                const msg = `RunOnce Error: ${err.message}`
                output += msg
                console.error(`[INIT ERROR:${name || '?'}] ${msg}`)
                if (name) this.addLog(name, msg)
                resolve({ success: false, output })
            })

            child.on('close', (code) => {
                resolve({ success: code === 0, output: output.trim() })
            })
        })
    }

    async startService(name: string, binPath: string, args: string[], shell: boolean = true): Promise<boolean> {
        if (this.processes.has(name)) {
            return true
        }

        if (!fs.existsSync(binPath)) {
            this.addLog(name, `Executable not found: ${binPath}`)
            return false
        }

        try {
            this.addLog(name, `Starting service with: ${binPath} ${args.join(' ')}`)

            const serviceEnv = { ...process.env }
            const binDirPath = path.dirname(binPath)
            if (serviceEnv.PATH) {
                serviceEnv.PATH = `${binDirPath}${path.delimiter}${serviceEnv.PATH}`
            } else {
                serviceEnv.PATH = binDirPath
            }

            const child = spawn(binPath, args, {
                shell: shell,
                windowsHide: true,
                stdio: ['ignore', 'pipe', 'pipe'],
                cwd: binDirPath,
                env: serviceEnv
            })

            console.log(`[PROCESS] Started ${name} with PID ${child.pid}`)
            this.addLog(name, `Process started (PID: ${child.pid})`)

            child.stdout?.on('data', (data) => this.addLog(name, data.toString()))
            child.stderr?.on('data', (data) => this.addLog(name, data.toString()))

            child.on('error', (err) => {
                this.addLog(name, `Failed to start: ${err.message}`)
                this.processes.delete(name)
            })

            child.on('exit', (code) => {
                this.addLog(name, `Exited with code ${code}`)
                if (code !== 0) {
                    this.checkErrorLogs(name)
                }
                this.processes.delete(name)
            })

            this.processes.set(name, child)

            return true
        } catch (error: any) {
            this.addLog(name, `Spawn Error: ${error.message}`)
            return false
        }
    }

    async stopService(name: string): Promise<boolean> {
        const child = this.processes.get(name)
        if (child) {
            this.addLog(name, 'Stopping service...')
            try {
                if (process.platform === 'win32') {
                    child.kill()
                } else {
                    // On Unix, try SIGTERM first, then SIGKILL if needed
                    child.kill('SIGTERM')
                }
            } catch (e) {
                this.addLog(name, `Stop error: ${e instanceof Error ? e.message : String(e)}`)
            }
            this.processes.delete(name)
        }

        // Also force kill lingering processes
        return this.forceKillAll(name)
    }

    async forceKillAll(name: string): Promise<boolean> {
        const isWindows = process.platform === 'win32';
        return new Promise((resolve) => {
            const childProcess = this.processes.get(name)

            if (isWindows) {
                if (childProcess && childProcess.pid) {
                    this.addLog(name, `Killing process by PID: ${childProcess.pid}`)
                    spawn('taskkill', ['/F', '/PID', childProcess.pid.toString(), '/T'], { windowsHide: true })
                        .on('close', () => { this.processes.delete(name); resolve(true) })
                        .on('error', () => { this.processes.delete(name); resolve(true) })
                    return
                }

                const isVersionedMariaDb = name.startsWith('mariadb:') || name.startsWith('mysql:')

                if (isVersionedMariaDb) {
                    const dbVersion = name.split(':')[1]
                    const dbId = `mariadb-${dbVersion}`

                    if (process.platform === 'win32') {
                        const psCommand = `Get-CimInstance Win32_Process -Filter "name = 'mariadbd.exe' OR name = 'mysqld.exe'" | Where-Object { $_.CommandLine -like '*${dbId}*' } | Invoke-CimMethod -MethodName Terminate`
                        spawn('powershell', ['-Command', psCommand], { windowsHide: true })
                            .on('close', () => resolve(true))
                            .on('error', () => resolve(true))
                    } else {
                        // Unix: pkill -f matches full command line (data directory path usually contains the dbId)
                        spawn('pkill', ['-f', dbId], { windowsHide: true })
                            .on('close', () => resolve(true))
                            .on('error', () => resolve(true))
                    }
                } else {
                    const isWindows = process.platform === 'win32';
                    if (isWindows) {
                        const executableName = name === 'nginx' ? 'nginx.exe' : name === 'apache' ? 'httpd.exe' : name === 'php' ? 'php-cgi.exe' : 'mariadbd.exe'
                        spawn('taskkill', ['/F', '/IM', executableName, '/T'], { windowsHide: true })
                            .on('close', () => resolve(true))
                            .on('error', () => resolve(true))
                    } else {
                        const processName = name === 'nginx' ? 'nginx' : name === 'apache' ? 'httpd' : name === 'php' ? 'php-cgi' : 'mariadbd'
                        spawn('pkill', ['-f', processName], { windowsHide: true })
                            .on('close', () => resolve(true))
                            .on('error', () => resolve(true))
                    }
                }
            } else {
                // macOS / Linux
                if (childProcess && childProcess.pid) {
                    try { process.kill(childProcess.pid, 'SIGKILL') } catch (e) { }
                    this.processes.delete(name)
                }

                const procName = name === 'nginx' ? 'nginx' : name === 'apache' ? 'httpd' : name === 'php' ? 'php-fpm' : 'mariadbd'
                spawn('pkill', ['-9', '-f', procName])
                    .on('close', () => resolve(true))
                    .on('error', () => resolve(true))
            }
        })
    }

    async killByPort(name: string, port: string | number): Promise<boolean> {
        const isWindows = process.platform === 'win32';
        return new Promise((resolve) => {
            if (!port) return resolve(true)
            this.addLog(name, `Checking if port ${port} is in use...`)

            if (isWindows) {
                const cmd = `netstat -ano | findstr :${port} | findstr LISTENING`
                const netstat = spawn('cmd.exe', ['/c', cmd], { windowsHide: true })
                let output = ''
                netstat.stdout?.on('data', d => output += d.toString())
                netstat.on('close', () => {
                    const lines = output.split('\n').filter(l => l.trim())
                    if (lines.length === 0) return resolve(true)
                    const pids = new Set<string>()
                    lines.forEach(line => {
                        const parts = line.trim().split(/\s+/)
                        const pid = parts[parts.length - 1]
                        if (pid && !isNaN(parseInt(pid))) pids.add(pid)
                    })
                    const kills = Array.from(pids).map(pid => new Promise<void>(res => {
                        spawn('taskkill', ['/F', '/PID', pid, '/T'], { windowsHide: true }).on('close', () => res())
                    }))
                    Promise.all(kills).then(() => resolve(true))
                })
            } else {
                const lsof = spawn('lsof', ['-i', `:${port}`, '-t'])
                let output = ''
                lsof.stdout?.on('data', d => output += d.toString())
                lsof.on('close', () => {
                    const pids = output.split('\n').filter(l => l.trim())
                    if (pids.length === 0) return resolve(true)
                    const kills = pids.map(pid => new Promise<void>(res => {
                        spawn('kill', ['-9', pid]).on('close', () => res())
                    }))
                    Promise.all(kills).then(() => resolve(true))
                })
            }
        })
    }

    isServiceRunning(name: string): boolean {
        return this.processes.has(name)
    }

    getStatuses(): Record<string, string> {
        const statuses: Record<string, string> = {}
        for (const name of Array.from(this.processes.keys())) {
            statuses[name] = 'running'
        }
        // Ensure defaults are present if stopped
        if (!statuses['nginx']) statuses['nginx'] = 'stopped'
        if (!statuses['apache']) statuses['apache'] = 'stopped'
        if (!statuses['mariadb']) statuses['mariadb'] = 'stopped'
        return statuses
    }

    private async checkErrorLogs(name: string) {
        if (!this.logDir) return
        const logPaths: Record<string, string> = {
            nginx: path.join(this.logDir, 'nginx_error.log'),
            apache: path.join(this.logDir, 'apache_error.log'),
            mariadb: path.join(this.logDir, 'mariadb_error.log')
        }

        const logPath = logPaths[name]
        if (logPath && fs.existsSync(logPath)) {
            try {
                const content = fs.readFileSync(logPath, 'utf8')
                const lines = content.split('\n').slice(-10) // Get last 10 lines
                this.addLog(name, '--- ERROR LOG CONTENT ---')
                lines.forEach(l => {
                    if (l.trim()) this.addLog(name, `[CORE ERROR] ${l}`)
                })
            } catch (e) {
                console.error('Failed to read error log:', e)
            }
        }
    }

    getLastLogs(name: string, count: number = 10): string[] {
        const serviceLogs = this.logs.get(name) || []
        return serviceLogs.slice(-count)
    }

    async getLastErrorLogs(name: string, count: number = 10): Promise<string[]> {
        this.initLogs()
        const foundLogs: string[] = []

        // 1. Check in-memory logs for [CORE ERROR] or other error markers
        const memLogs = this.logs.get(name) || []
        const errorMarkers = ['ERROR', 'failed', 'Fatal', 'error']
        const recentErrors = memLogs.filter(line =>
            errorMarkers.some(m => line.toLowerCase().includes(m.toLowerCase()))
        ).slice(-count)

        foundLogs.push(...recentErrors)

        // 2. Check physical log files if memory logs are insufficient
        const sanitizedName = name.replace(/:/g, '_')
        const possibleFiles = [
            path.join(this.logDir, `${sanitizedName}.log`),
            path.join(this.logDir, `${sanitizedName}_error.log`),
            path.join(this.logDir, `mariadb_error.log`),
            path.join(this.logDir, `nginx_error.log`)
        ]

        for (const logPath of possibleFiles) {
            if (fs.existsSync(logPath)) {
                try {
                    const content = fs.readFileSync(logPath, 'utf8')
                    const lines = content.split(/\r?\n/).filter(l => l.trim())
                    const relevantLines = lines.slice(-count)
                    relevantLines.forEach(l => {
                        if (!foundLogs.includes(l)) foundLogs.push(l)
                    })
                } catch (e) {
                    // Ignore errors
                }
            }
        }

        return Array.from(new Set(foundLogs)).slice(-count)
    }

    stopAll(): void {
        for (const name of this.processes.keys()) {
            this.stopService(name)
        }
    }
}

export const processManager = new ProcessManager()
app.on('before-quit', () => processManager.stopAll())
