<?php

namespace App\Services\Database;

use App\Contracts\DatabaseDriverInterface;
use App\Contracts\SchemaDiscoveryInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Config;

class MySqlDriver implements DatabaseDriverInterface, SchemaDiscoveryInterface
{
    protected string $connectionName = 'mariavel_dynamic';

    public function connect(array $config): bool
    {
        Config::set("database.connections.{$this->connectionName}", [
            'driver' => 'mysql',
            'host' => $config['host'] ?? '127.0.0.1',
            'port' => $config['port'] ?? '3306',
            'database' => $config['database'] ?? null,
            'username' => $config['username'] ?? 'root',
            'password' => $config['password'] ?? '',
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'strict' => false,
            'engine' => null,
        ]);

        try {
            DB::purge($this->connectionName);
            DB::connection($this->connectionName)->getPdo();
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    public function query(string $sql, array $bindings = []): array
    {
        return DB::connection($this->connectionName)->select($sql, $bindings);
    }

    public function getConnection()
    {
        return DB::connection($this->connectionName);
    }

    public function getDatabases(): array
    {
        $results = $this->query('SHOW DATABASES');
        return array_map(fn($db) => $db->Database, $results);
    }

    public function getTables(): array
    {
        $results = $this->query('SHOW TABLES');
        $key = "Tables_in_" . DB::connection($this->connectionName)->getDatabaseName();
        return array_map(fn($table) => $table->$key, $results);
    }

    public function getTableSchema(string $table): array
    {
        return $this->query("DESCRIBE `{$table}`");
    }

    public function getTableData(string $table, int $limit = 100, int $offset = 0, array $filters = []): array
    {
        $query = DB::connection($this->connectionName)->table($table);
        $this->applyFilters($query, $filters);
        return $query->limit($limit)->offset($offset)->get()->all();
    }

    public function getTableCount(string $table, array $filters = []): int
    {
        $query = DB::connection($this->connectionName)->table($table);
        $this->applyFilters($query, $filters);
        return $query->count();
    }

    protected function applyFilters($query, array $filters)
    {
        if (empty($filters)) return $query;

        foreach ($filters as $filter) {
            $field = $filter['field'] ?? ($filter['columnField'] ?? null);
            $operator = $filter['operator'] ?? ($filter['operatorValue'] ?? null);
            $value = $filter['value'] ?? null;

            if (!$field) continue;

            switch ($operator) {
                case 'contains':
                    $query->where($field, 'LIKE', "%{$value}%");
                    break;
                case 'equals':
                case '=':
                    $query->where($field, '=', $value);
                    break;
                case 'startsWith':
                    $query->where($field, 'LIKE', "{$value}%");
                    break;
                case 'endsWith':
                    $query->where($field, 'LIKE', "%{$value}");
                    break;
                case 'isEmpty':
                    $query->where(function($q) use ($field) {
                        $q->whereNull($field)->orWhere($field, '');
                    });
                    break;
                case 'isNotEmpty':
                    $query->whereNotNull($field)->where($field, '!=', '');
                    break;
                case 'isAnyOf':
                    if (is_array($value)) {
                        $query->whereIn($field, $value);
                    }
                    break;
                case '>':
                case '<':
                case '>=':
                case '<=':
                case '!=':
                    $query->where($field, $operator, $value);
                    break;
            }
        }
        return $query;
    }

    public function getForeignKeys(string $table): array
    {
        $sql = "
            SELECT 
                COLUMN_NAME, 
                REFERENCED_TABLE_NAME, 
                REFERENCED_COLUMN_NAME 
            FROM 
                INFORMATION_SCHEMA.KEY_COLUMN_USAGE 
            WHERE 
                TABLE_NAME = ? AND 
                REFERENCED_TABLE_NAME IS NOT NULL AND 
                TABLE_SCHEMA = ?
        ";

        $dbName = DB::connection($this->connectionName)->getDatabaseName();
        return $this->query($sql, [$table, $dbName]);
    }

    public function export(array $config, array $filters = []): string
    {
        $database = $config['database'] ?? '';
        $table = $config['table'] ?? '';
        
        // If filters are provided, we do a manual export for the filtered rows
        if (!empty($filters) && !empty($table)) {
            return $this->exportFilteredTable($database, $table, $filters);
        }

        $filename = !empty($table) 
            ? "{$table}-" . date('Y-m-d') . ".sql" 
            : "backup-" . ($database ?: 'all') . "-" . date('Y-m-d') . ".sql";

        $directory = storage_path('app/backups');
        if (!is_dir($directory)) {
            mkdir($directory, 0755, true);
        }

        $path = $directory . DIRECTORY_SEPARATOR . $filename;
        $errorPath = $directory . DIRECTORY_SEPARATOR . $filename . '.err';
        
        $mysqldumpPath = env('MYSQLDUMP_PATH', 'mysqldump');
        
        $username = $config['username'] ?? 'root';
        $password = $config['password'] ?? '';
        $host = $config['host'] ?? '127.0.0.1';
        $port = $config['port'] ?? '3306';

        // Build command with flags for a complete and resilient export
        $passwordPart = !empty($password) ? "-p" . escapeshellarg($password) : "";
        
        if (!empty($table) && !empty($database)) {
            $dbPart = escapeshellarg($database) . " " . escapeshellarg($table);
        } else {
            $dbPart = !empty($database) ? escapeshellarg($database) : "--all-databases";
        }
        
        // --single-transaction: for InnoDB tables, ensures consistency without locking
        // --skip-lock-tables: avoids issues with views/definers that might prevent locking
        // --routines, --triggers, --events: ensures all database objects are included
        // --quick: useful for large tables
        $flags = "--single-transaction --skip-lock-tables --routines --triggers --events --quick";

        $command = sprintf(
            '%s -u %s %s -h %s -P %s %s %s > %s 2> %s',
            $mysqldumpPath === 'mysqldump' ? 'mysqldump' : escapeshellarg($mysqldumpPath),
            escapeshellarg($username),
            $passwordPart,
            escapeshellarg($host),
            escapeshellarg($port),
            $flags,
            $dbPart,
            escapeshellarg($path),
            escapeshellarg($errorPath)
        );

        shell_exec($command);

        if (file_exists($errorPath)) {
            $errors = file_get_contents($errorPath);
            unlink($errorPath);
            
            // Some errors are fatal even if some output was produced (like the definer issue)
            if (!empty(trim($errors)) && (str_contains(strtolower($errors), 'error') || !file_exists($path) || filesize($path) < 1000)) {
                // If it's just a warning (like SSL), we might want to continue, 
                // but if it's a "Got error", we should probably fail.
                if (str_contains(strtolower($errors), 'error')) {
                    throw new \Exception("Export failed: " . $errors);
                }
            }
        }

        if (!file_exists($path) || filesize($path) === 0) {
            $instruction = "The 'mysqldump' binary was not found. Please set MYSQLDUMP_PATH in your .env file to the absolute path of the mysqldump executable.";
            throw new \Exception("Export failed: Binary not found.\n\nInstructions: " . $instruction);
        }

        return $path;
    }

    public function import(array $config, string $filePath): bool
    {
        $directory = storage_path('app/backups');
        if (!is_dir($directory)) {
            mkdir($directory, 0755, true);
        }
        $errorPath = $directory . DIRECTORY_SEPARATOR . 'import_error.err';
        
        $mysqlPath = env('MYSQL_BINARY_PATH', 'mysql');

        $username = $config['username'] ?? 'root';
        $password = $config['password'] ?? '';
        $host = $config['host'] ?? '127.0.0.1';
        $port = $config['port'] ?? '3306';
        $database = $config['database'] ?? '';

        $passwordPart = !empty($password) ? "-p" . escapeshellarg($password) : "";
        $dbPart = !empty($database) ? escapeshellarg($database) : "";

        $command = sprintf(
            '%s -u %s %s -h %s -P %s %s < %s 2> %s',
            $mysqlPath === 'mysql' ? 'mysql' : escapeshellarg($mysqlPath),
            escapeshellarg($username),
            $passwordPart,
            escapeshellarg($host),
            escapeshellarg($port),
            $dbPart,
            escapeshellarg($filePath),
            escapeshellarg($errorPath)
        );

        shell_exec($command);

        if (file_exists($errorPath)) {
            $errors = file_get_contents($errorPath);
            unlink($errorPath);
            if (!empty(trim($errors))) {
                $instruction = "Please check your .env file and ensure MYSQL_BINARY_PATH is correct. Example: MYSQL_BINARY_PATH=C:\\xampp\\mysql\\bin\\mysql.exe";
                throw new \Exception("Import failed: " . $errors . "\n\nInstructions: " . $instruction);
            }
        }

        return true;
    }

    public function getDatabaseMetadata(string $database): array
    {
        $sql = "
            SELECT 
                s.DEFAULT_CHARACTER_SET_NAME as charset,
                s.DEFAULT_COLLATION_NAME as collation,
                (SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = s.SCHEMA_NAME) as size_bytes,
                (SELECT COUNT(*) FROM information_schema.TABLES WHERE table_schema = s.SCHEMA_NAME) as table_count,
                (SELECT COUNT(*) FROM information_schema.VIEWS WHERE table_schema = s.SCHEMA_NAME) as view_count
            FROM 
                information_schema.SCHEMATA s
            WHERE 
                s.SCHEMA_NAME = ?
        ";

        $results = $this->query($sql, [$database]);
        return (array) ($results[0] ?? []);
    }

    public function getTableMetadata(string $database, string $table): array
    {
        $sql = "
            SELECT 
                ENGINE as `engine`,
                TABLE_ROWS as `rows`,
                DATA_LENGTH as `data_length`,
                INDEX_LENGTH as `index_length`,
                DATA_FREE as `data_free`,
                AUTO_INCREMENT as `auto_increment`,
                CREATE_TIME as `create_time`,
                UPDATE_TIME as `update_time`,
                TABLE_COLLATION as `collation`,
                TABLE_COMMENT as `comment`
            FROM 
                information_schema.TABLES 
            WHERE 
                TABLE_SCHEMA = ? AND 
                TABLE_NAME = ?
        ";

        $results = $this->query($sql, [$database, $table]);
        return (array) ($results[0] ?? []);
    }

    public function truncateTable(string $table): bool
    {
        DB::connection($this->connectionName)->statement("TRUNCATE TABLE `{$table}`");
        return true;
    }

    public function dropTable(string $table): bool
    {
        DB::connection($this->connectionName)->statement("DROP TABLE `{$table}`");
        return true;
    }

    public function optimizeTable(string $table): bool
    {
        DB::connection($this->connectionName)->statement("OPTIMIZE TABLE `{$table}`");
        return true;
    }

    public function getTablesMetadata(string $database): array
    {
        $sql = "
            SELECT 
                TABLE_NAME as `name`,
                ENGINE as `engine`,
                TABLE_ROWS as `rows`,
                DATA_LENGTH as `data_length`,
                INDEX_LENGTH as `index_length`,
                DATA_FREE as `data_free`,
                AUTO_INCREMENT as `auto_increment`,
                CREATE_TIME as `create_time`,
                UPDATE_TIME as `update_time`,
                TABLE_COLLATION as `collation`,
                TABLE_COMMENT as `comment`
            FROM 
                information_schema.TABLES 
            WHERE 
                TABLE_SCHEMA = ?
            ORDER BY 
                TABLE_NAME ASC
        ";

        return $this->query($sql, [$database]);
    }

    public function batchUpdate(string $table, array $changes): bool
    {
        $connection = $this->getConnection();
        
        // Find primary key
        $schema = $this->getTableSchema($table);
        $primaryKey = 'id'; // default
        foreach ($schema as $col) {
            if (($col->Key ?? '') === 'PRI') {
                $primaryKey = $col->Field;
                break;
            }
        }

        $connection->beginTransaction();
        try {
            foreach ($changes as $change) {
                if (!isset($change[$primaryKey])) {
                    continue;
                }
                
                $id = $change[$primaryKey];
                $updateData = $change;
                unset($updateData[$primaryKey]);
                
                if (empty($updateData)) continue;

                $connection->table($table)->where($primaryKey, $id)->update($updateData);
            }
            $connection->commit();
            return true;
        } catch (\Exception $e) {
            $connection->rollBack();
            throw $e;
        }
    }

    protected function exportFilteredTable(string $database, string $table, array $filters): string
    {
        $query = DB::connection($this->connectionName)->table($table);
        $this->applyFilters($query, $filters);
        $rows = $query->get();

        $filename = "{$table}-filtered-" . date('Y-m-d-His') . ".sql";
        $directory = storage_path('app/backups');
        if (!is_dir($directory)) {
            mkdir($directory, 0755, true);
        }
        $path = $directory . DIRECTORY_SEPARATOR . $filename;

        $handle = fopen($path, 'w');
        
        fwrite($handle, "-- Mariavel Filtered SQL Export\n");
        fwrite($handle, "-- Database: {$database}\n");
        fwrite($handle, "-- Table: {$table}\n");
        fwrite($handle, "-- Date: " . date('Y-m-d H:i:s') . "\n\n");

        $createTableResults = $this->query("SHOW CREATE TABLE `{$table}`");
        if (!empty($createTableResults)) {
            $createTable = $createTableResults[0];
            $createSql = $createTable->{'Create Table'} ?? '';
            if ($createSql) {
                fwrite($handle, "DROP TABLE IF EXISTS `{$table}`;\n");
                fwrite($handle, $createSql . ";\n\n");
            }
        }

        if ($rows->count() > 0) {
            foreach ($rows as $row) {
                $fields = [];
                $values = [];
                foreach ((array)$row as $field => $value) {
                    $fields[] = "`{$field}`";
                    if (is_null($value)) {
                        $values[] = "NULL";
                    } else {
                        $values[] = DB::connection($this->connectionName)->getPdo()->quote($value);
                    }
                }
                $sql = "INSERT INTO `{$table}` (" . implode(', ', $fields) . ") VALUES (" . implode(', ', $values) . ");\n";
                fwrite($handle, $sql);
            }
        }

        fclose($handle);
        return $path;
    }

    public function createDatabase(string $name, string $charset = 'utf8mb4', string $collation = 'utf8mb4_unicode_ci'): bool
    {
        DB::connection($this->connectionName)->statement("CREATE DATABASE `{$name}` CHARACTER SET {$charset} COLLATE {$collation}");
        return true;
    }
}
