import fs from 'fs'
import path from 'path'
import { spawn } from 'child_process'
import decompress from 'decompress'
import zlib from 'zlib'
import { pipeline, PassThrough } from 'stream'

export interface MariaDbConfig {
    binPath: string
    host: string
    port: string
    rootUser: string
    rootPass: string
}

export class MariaDbOperationService {
    async extractArchive(archivePath: string, targetDir: string): Promise<string[]> {
        if (!fs.existsSync(targetDir)) {
            fs.mkdirSync(targetDir, { recursive: true })
        }

        const lowerPath = archivePath.toLowerCase()
        if (lowerPath.endsWith('.gz') && !lowerPath.endsWith('.tar.gz')) {
            // Handle single-file gzip compression like .sql.gz
            const baseName = path.basename(archivePath, '.gz')
            const outPath = path.join(targetDir, baseName.endsWith('.sql') ? baseName : `${baseName}.sql`)

            const source = fs.createReadStream(archivePath)
            const destination = fs.createWriteStream(outPath)
            const unzip = zlib.createUnzip()

            await new Promise((resolve, reject) => {
                source.pipe(unzip).pipe(destination)
                    .on('finish', () => resolve(true))
                    .on('error', reject)
            })

            return [outPath]
        }

        // Process standard archives (tar, zip, tar.gz)
        await decompress(archivePath, targetDir)

        // Find all .sql files in the extracted directory
        const sqlFiles: string[] = []
        const findSqlFiles = (dir: string) => {
            const files = fs.readdirSync(dir)
            for (const file of files) {
                const fullPath = path.join(dir, file)
                if (fs.statSync(fullPath).isDirectory()) {
                    findSqlFiles(fullPath)
                } else if (file.toLowerCase().endsWith('.sql')) {
                    sqlFiles.push(fullPath)
                }
            }
        }

        findSqlFiles(targetDir)
        return sqlFiles
    }

    async runQuery(config: MariaDbConfig, query: string, database?: string): Promise<{ success: boolean; output: string; error?: string }> {
        return new Promise((resolve) => {
            const mariaDbPath = config.binPath;
            const args = [
                `--host=${config.host}`,
                `--port=${config.port}`,
                `--user=${config.rootUser}`,
                database ? `--database=${database}` : null
            ].filter(Boolean) as string[]

            if (config.rootPass) {
                args.push(`--password=${config.rootPass}`)
            }

            args.push('-e', query)

            const child = spawn(mariaDbPath, args, { windowsHide: true })
            let output = ''
            let error = ''

            child.stdout.on('data', (data) => output += data.toString())
            child.stderr.on('data', (data) => error += data.toString())

            child.on('error', (err) => {
                resolve({ success: false, output, error: err.message })
            })

            child.on('close', (code) => {
                if (code === 0) {
                    resolve({ success: true, output })
                } else {
                    resolve({ success: false, output, error })
                }
            })
        })
    }

    async checkDatabaseExists(config: MariaDbConfig, dbName: string): Promise<boolean> {
        const result = await this.runQuery(config, `SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '${dbName}'`)
        return result.success && result.output.includes(dbName)
    }

    async createDatabaseAndUser(config: MariaDbConfig, dbName: string, user: string, pass: string): Promise<{ success: boolean; message: string }> {
        const queries = [
            `CREATE DATABASE IF NOT EXISTS \`${dbName}\` CHARACTER SET utf8 COLLATE utf8_unicode_ci;`,
            `CREATE USER IF NOT EXISTS '${user}'@'%' IDENTIFIED BY '${pass}';`,
            `GRANT ALL PRIVILEGES ON \`${dbName}\`.* TO '${user}'@'%';`,
            `FLUSH PRIVILEGES;`
        ]

        for (const query of queries) {
            const result = await this.runQuery(config, query)
            if (!result.success) {
                return { success: false, message: `Query failed: ${query}\nError: ${result.error}` }
            }
        }

        return { success: true, message: 'Database and user created successfully.' }
    }

    async dropDatabase(config: MariaDbConfig, dbName: string): Promise<{ success: boolean; message: string }> {
        const result = await this.runQuery(config, `DROP DATABASE IF EXISTS \`${dbName}\``)
        return { success: result.success, message: result.success ? 'Database dropped' : `Drop failed: ${result.error}` }
    }

    async importSql(config: MariaDbConfig, dbName: string, sqlFilePath: string, onProgress?: (log: string) => void): Promise<{ success: boolean; message: string }> {
        return new Promise((resolve) => {
            const mariaDbPath = config.binPath
            const args = [
                `--host=${config.host}`,
                `--port=${config.port}`,
                `--user=${config.rootUser}`,
                `--database=${dbName}`,
                '--max_allowed_packet=1G'
            ]

            if (config.rootPass) {
                args.push(`--password=${config.rootPass}`)
            }

            onProgress?.(`Executing: "${mariaDbPath}" ${args.join(' ')} < "${sqlFilePath}"`)

            const child = spawn(mariaDbPath, args, { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] })

            // Create a wrapper stream to prepend/append SQL commands
            const combinedStream = new PassThrough()

            // Send optimization headers
            combinedStream.write('SET GLOBAL innodb_strict_mode = 0;\n')
            combinedStream.write('SET SESSION innodb_strict_mode = 0;\n')
            combinedStream.write('SET foreign_key_checks = 0;\n')
            combinedStream.write('SET unique_checks = 0;\n')
            combinedStream.write('SET autocommit = 0;\n')
            combinedStream.write('SET sql_log_bin = 0;\n')

            const fileStream = fs.createReadStream(sqlFilePath)

            // Use pipeline for robust error handling and backpressure
            pipeline(
                fileStream,
                combinedStream,
                child.stdin,
                (err) => {
                    if (err) {
                        if (err.code === 'EPIPE' || err.code === 'ECONNRESET') {
                            onProgress?.(`Notice: Pipe closed early (${err.code}). This is often normal if MariaDB finished early.`)
                        } else if (err.code === 'ENOBUFS') {
                            onProgress?.(`Error: System buffer overflow (ENOBUFS). The file might be too large for current system limits.`)
                        } else {
                            onProgress?.(`Stream error: ${err.message}`)
                        }
                    }
                }
            )

            // When file ends, send footer
            fileStream.on('end', () => {
                // Ensure we don't write to a closed stream
                if (!combinedStream.writableEnded) {
                    combinedStream.write('\nCOMMIT;\n')
                    combinedStream.write('SET foreign_key_checks = 1;\n')
                    combinedStream.write('SET unique_checks = 1;\n')
                    combinedStream.write('SET autocommit = 1;\n')
                    combinedStream.end()
                }
            })

            child.stdout.on('data', (data) => onProgress?.(data.toString()))
            child.stderr.on('data', (data) => onProgress?.(data.toString()))

            child.on('error', (err) => {
                onProgress?.(`Process error: ${err.message}`)
                resolve({ success: false, message: `Process error: ${err.message}` })
            })

            child.on('close', (code) => {
                if (code === 0) {
                    onProgress?.('Import completed successfully.')
                    resolve({ success: true, message: 'Import completed successfully.' })
                } else {
                    onProgress?.(`Import failed with exit code ${code}`)
                    resolve({ success: false, message: `Import failed (Exit Code: ${code})` })
                }
            })
        })
    }

    async listDatabases(config: MariaDbConfig): Promise<string[]> {
        const result = await this.runQuery(config, 'SHOW DATABASES')
        if (!result.success) return []

        return result.output
            .split('\n')
            .map(line => line.trim())
            .filter(line => line && line !== 'Database' && !line.match(/^information_schema|performance_schema|mysql|sys$/i))
    }

    async exportSql(config: MariaDbConfig, dbName: string, targetPath: string, compress: boolean, onProgress?: (log: string) => void): Promise<{ success: boolean; message: string }> {
        return new Promise((resolve) => {
            const isWindows = process.platform === 'win32';
            const binDir = path.dirname(config.binPath);
            const dumpBinary = isWindows ? 'mariadb-dump.exe' : 'mariadb-dump';
            const mysqlDumpFallback = isWindows ? 'mysqldump.exe' : 'mysqldump';

            let dumpPath = path.join(binDir, dumpBinary);
            if (!fs.existsSync(dumpPath)) {
                dumpPath = path.join(binDir, mysqlDumpFallback);
            }

            if (!fs.existsSync(dumpPath)) {
                onProgress?.(`Error: MariaDB/MySQL dump binary not found in ${binDir}`);
                return resolve({ success: false, message: 'Dump binary (mariadb-dump or mysqldump) not found.' });
            }

            const args = [
                `--host=${config.host}`,
                `--port=${config.port}`,
                `--user=${config.rootUser}`,
                '--quick',
                '--single-transaction',
                '--max_allowed_packet=1G',
                dbName,
                '--routines',
                '--triggers'
            ]

            if (config.rootPass) {
                args.push(`--password=${config.rootPass}`)
            }

            onProgress?.(`Executing: "${dumpPath}" ${args.join(' ')} > "${targetPath}"`)

            const child = spawn(dumpPath, args, { windowsHide: true })
            const writeStream = fs.createWriteStream(targetPath)

            if (compress) {
                const gzip = zlib.createGzip()
                child.stdout.pipe(gzip).pipe(writeStream)
            } else {
                child.stdout.pipe(writeStream)
            }

            let errorOutput = ''
            child.stderr.on('data', (data) => {
                errorOutput += data.toString()
                onProgress?.(data.toString())
            })

            child.on('error', (err) => {
                onProgress?.(`Process error: ${err.message}`)
                resolve({ success: false, message: `Process error: ${err.message}` })
            })

            child.on('close', (code) => {
                writeStream.end()
                if (code === 0) {
                    onProgress?.('Export completed successfully.')
                    resolve({ success: true, message: 'Export completed successfully.' })
                } else {
                    onProgress?.(`Export failed with exit code ${code}`)
                    resolve({ success: false, message: `Export failed (Exit Code: ${code}).\n${errorOutput}` })
                }
            })
        })
    }
}

export const mariaDbOperationService = new MariaDbOperationService()
