<?php

namespace App\Traits;

use App\Enums\StatusEnum;
use App\Models\Core\Setting;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Schema;

trait InstallerManager
{

    private function _isPurchased() :bool{

        $purchaseKey = site_settings('purchase_key');
        $userName    = site_settings('envato_username');

        $licenseData = Cache::get('software_license');

         if (empty($purchaseKey) || empty($userName)) {
             return false;
         }

         if (!$licenseData) {
             if (!$this->_registerDomain() || !$this->_validatePurchaseKey($purchaseKey , $userName)) {
                 return false;
             }
             Cache::put('software_license', true, now()->addHour());
         }

         return true;

     }



     public function is_installed() :bool{

        try {
            $logFile = storage_path(base64_decode(config('installer.cacheFile')));
            $tableName = 'settings';
            DB::connection()->getPdo();
            if(!DB::connection()->getDatabaseName() || !file_exists($logFile) || !Schema::hasTable($tableName) ) return false;
            return true;
        } catch (\Exception $ex) {
            return false;
        }
    }


    public function checkRequirements(array $requirements) :array{


        $results = [];

        foreach ($requirements as $type => $requirement) {
            switch ($type) {

                case 'php':
                    foreach ($requirements[$type] as $requirement) {
                        $results['requirements'][$type][$requirement] = true;

                        if (! extension_loaded($requirement)) {
                            $results['requirements'][$type][$requirement] = false;

                            $results['errors'] = true;
                        }
                    }
                    break;
                case 'apache':
                    foreach ($requirements[$type] as $requirement) {
                        if (function_exists('apache_get_modules')) {
                            $results['requirements'][$type][$requirement] = true;

                            if (! in_array($requirement, apache_get_modules())) {
                                $results['requirements'][$type][$requirement] = false;

                                $results['errors'] = true;
                            }
                        }
                    }
                    break;
            }
        }

        return $results;

    }




    /**
     * Get current Php version information.
     *
     * @return array
     */
    private static function getPhpVersionInfo()
    {
        $currentVersionFull = PHP_VERSION;
        preg_match("#^\d+(\.\d+)*#", $currentVersionFull, $filtered);
        $currentVersion = $filtered[0];

        return [
            'full'    => $currentVersionFull,
            'version' => $currentVersion,
        ];
    }




    /**
     * Check PHP version requirement.
     *
     * @return array
     */
    public function checkPHPversion(string $minPhpVersion) :array
    {
        $minVersionPhp = $minPhpVersion;
        $currentPhpVersion = $this->getPhpVersionInfo();
        $supported = false;

        if (version_compare($currentPhpVersion['version'], $minVersionPhp) >= 0) {
            $supported = true;
        }

        $phpStatus = [
            'full'       => $currentPhpVersion['full'],
            'current'    => $currentPhpVersion['version'],
            'minimum'    => $minVersionPhp,
            'supported'  => $supported,
        ];

        return $phpStatus;
    }



    public function permissionsCheck(array $folders) :array{

        $permissions = [];
        foreach ($folders as $folder => $permission) {
            $isWritable = $this->checkWritablePermission($folder);
            $permissions[$folder] = $isWritable;
        }

        return $permissions;


    }


    /**
     * Check if folder/file is writable.
     *
     * @param $folder
     * @return bool
     */
    private function checkWritablePermission($folder)
    {
        $path = base_path($folder);
        
        // Check if path exists first
        if (!file_exists($path)) {
            return false;
        }
        
        // For files, check if writable
        if (is_file($path)) {
            return is_writable($path);
        }
        
        // For directories, check if writable and try to create a test file
        if (is_dir($path)) {
            if (!is_writable($path)) {
                return false;
            }
            
            // Try to create a test file to confirm write access
            $testFile = $path . DIRECTORY_SEPARATOR . 'test_write_' . time() . '.tmp';
            $result = @file_put_contents($testFile, 'test');
            
            if ($result !== false) {
                @unlink($testFile);
                return true;
            }
            
            return false;
        }
        
        return false;
    }

    /**
     * Get a folder permission.
     *
     * @param $folder
     * @return string
     */
    private function getPermission($folder)
    {
        return substr(sprintf('%o', fileperms(base_path($folder))), -4);
    }


    /**
     * Add the file and set the errors.
     *
     * @param $folder
     * @param $permission
     * @param $isSet
     */
    private function addFileAndSetErrors($folder, $permission, $isSet) :array
    {
        return $this->addFile($folder, $permission, $isSet);
    }


    /**
     * Add the file to the list of results.
     *
     * @param $folder
     * @param $permission
     * @param $isSet
     */
    private function addFile($folder, $permission, $isSet) :array
    {
        return [
            'folder' => $folder,
            'permission' => $permission,
            'isSet' => $isSet,
        ];

    }



    private function _envatoVerification(Request $request) : mixed {

        return $this->_validatePurchaseKey($request->input(base64_decode('cHVyY2hhc2VfY29kZQ==')) , $request->input(base64_decode('dXNlcm5hbWU=')));


    }



    private function _registerDomain()
    {
        try {
            $domain = parse_url(url('/'), PHP_URL_HOST) ?? 'localhost';
            $serverSignature = php_uname('n') . PHP_VERSION;
            $fingerprint = hash('sha256', $domain . $serverSignature . config('app.key'));

            $params = [
                'domain'        => $domain,
                'software_id'   => config('installer.software_id'),
                'version'       => config('installer.version'),
                'fingerprint'   => $fingerprint
            ];

            $url = 'https://verifylicense.online/api/licence-verification/register-domain';

            \Log::info('Registering domain', [
                'domain' => $domain,
                'software_id' => config('installer.software_id'),
                'fingerprint' => substr($fingerprint, 0, 10) . '...'
            ]);

            $response = Http::timeout(20)->post($url, $params);
            $data = $response->json();

            if (!isset($data['success'], $data['code'], $data['message'])) {
                \Log::warning('Domain registration failed: Invalid response structure');
                return false;
            }

            \Log::info('Domain registration result', [
                'success' => $data['success'],
                'message' => $data['message'] ?? 'No message'
            ]);

            return $data['success'];

        } catch (\Exception $e) {
            \Log::error('Domain registration exception', [
                'error' => $e->getMessage()
            ]);
            return false;
        }
    }

    private function _validatePurchaseKey(string $key , string $username) :mixed {

        $domain = parse_url(url('/'), PHP_URL_HOST) ?? 'localhost';
        $serverSignature = php_uname('n') . PHP_VERSION;
        $fingerprint = hash('sha256', $domain . $serverSignature . config('app.key'));

        $params['domain']               = $domain;
        $params['software_id']          = config('installer.software_id');
        $params['version']              = config('installer.version');
        $params['purchase_key']         = $key;
        $params['envato_username']      = $username;
        $params['fingerprint']          = $fingerprint;

        try {
            $url = 'https://verifylicense.online/api/licence-verification/verify-purchase';

            \Log::info('Sending license verification request', [
                'url' => $url,
                'domain' => $params['domain'],
                'software_id' => $params['software_id'],
                'version' => $params['version'],
                'username' => $username,
                'fingerprint' => substr($fingerprint, 0, 10) . '...'
            ]);

            $response = Http::timeout(30)->post($url, $params);

            \Log::info('License verification response received', [
                'status' => $response->status(),
                'body' => $response->body()
            ]);

            if (!$response->successful()) {
                \Log::error('License verification API returned error', [
                    'status' => $response->status(),
                    'body' => $response->body()
                ]);
                return false;
            }

            $data = $response->json();

            if (!isset($data['success'], $data['code'], $data['message'])) {
                \Log::error('License verification response missing required fields', [
                    'response' => $data
                ]);
                return false;
            }

            \Log::info('License verification result', [
                'success' => $data['success'],
                'code' => $data['code'],
                'message' => $data['message']
            ]);

            return $data['success'];

        } catch (\Exception $e) {
            \Log::error('License verification exception', [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            return false;
        }

    }




    private  function _chekcDbConnection(Request $request) :array {

        try {
            // Use Laravel's config to test the connection
            $host = $request->input('db_host');
            $port = $request->input('db_port');
            $database = $request->input('db_name');
            $username = $request->input('db_user');
            $password = $request->input('db_password');

            \Log::info('Starting database connection test', [
                'host' => $host,
                'port' => $port,
                'database' => $database,
                'username' => $username,
                'password_provided' => !empty($password)
            ]);

            // Test connection with the specific database directly
            $dsn = "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4";

            \Log::info('Testing connection with DSN', ['dsn' => "mysql:host={$host};port={$port};dbname={$database}"]);

            try {
                $pdo = new \PDO($dsn, $username, $password, [
                    \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
                    \PDO::ATTR_TIMEOUT => 5, // Shorter timeout
                ]);

                // Test a simple query to ensure full connectivity
                $result = $pdo->query('SELECT 1 as test');
                $row = $result->fetch();

                if ($row && $row['test'] == 1) {
                    \Log::info('Database connection test successful - query executed');
                    return ['success' => true];
                } else {
                    \Log::error('Database query failed unexpectedly');
                    return [
                        'success' => false,
                        'error' => 'Database connection established but queries are not working properly.'
                    ];
                }

            } catch(\PDOException $pdoError) {
                $errorCode = $pdoError->getCode();
                $errorMessage = $pdoError->getMessage();

                \Log::error('PDO Connection failed', [
                    'error' => $errorMessage,
                    'code' => $errorCode,
                    'dsn' => "mysql:host={$host};port={$port};dbname={$database}"
                ]);

                // Return specific error message based on error code
                return [
                    'success' => false,
                    'error' => $this->_getSpecificErrorMessage($pdoError, $database, $username)
                ];
            }

        } catch(\Exception $exception){
            \Log::error('Database connection failed (General Exception)', [
                'error' => $exception->getMessage(),
                'type' => get_class($exception)
            ]);

            return [
                'success' => false,
                'error' => 'Unable to connect to database server. Please check if MySQL/MariaDB is running and accessible.'
            ];
        }

    }

    /**
     * Get specific error message based on PDO exception
     */
    private function _getSpecificErrorMessage(\PDOException $e, string $database, string $username): string
    {
        $errorCode = $e->getCode();
        $errorMessage = $e->getMessage();

        \Log::info('Processing specific error message', [
            'code' => $errorCode,
            'message' => $errorMessage,
            'database' => $database,
            'username' => $username
        ]);

        // Check for specific MySQL error codes
        switch ($errorCode) {
            case 1045: // Access denied for user
                return "Incorrect username or password. Please verify your MySQL credentials and try again.";

            case 1049: // Unknown database
                return "Database '{$database}' not found. Please create the database or check the name is correct.";

            case 2002: // Connection refused
            case 2003: // Can't connect to MySQL server
                return "Cannot connect to MySQL server. Please check if MySQL is running and verify your host/port settings.";

            case 1044: // Access denied to database
                return "Database access denied. Please ensure the user has proper permissions for this database.";

            case 1698: // Access denied for user (auth_socket plugin)
                return "Authentication failed. Please check your MySQL user configuration and try again.";

            default:
                // Check error message content for more specific errors
                if (strpos($errorMessage, 'Access denied') !== false) {
                    if (strpos($errorMessage, 'using password: NO') !== false) {
                        return "Password required. Please provide your database password.";
                    } else {
                        return "Access denied. Please check your database credentials.";
                    }
                }

                if (strpos($errorMessage, 'Unknown database') !== false) {
                    return "Database not found. Please create the database first.";
                }

                if (strpos($errorMessage, 'Connection refused') !== false) {
                    return "Connection refused. Please ensure MySQL is running and accessible.";
                }

                if (strpos($errorMessage, "Can't connect") !== false) {
                    return "Cannot connect to database server. Please check your host and port settings.";
                }

                // Clean fallback error message (avoid showing technical details)
                return "Database connection failed. Please verify your database settings and try again.";
        }
    }

    /**
     * Get user-friendly error message for credential issues (deprecated - keeping for compatibility)
     */
    private function _getCredentialErrorMessage(\PDOException $e): string
    {
        $errorCode = $e->getCode();
        $errorMessage = $e->getMessage();

        // Check for specific MySQL error codes
        switch ($errorCode) {
            case 1045: // Access denied
                return 'Database username or password is incorrect. Please verify your MySQL credentials.';

            case 2002: // Connection refused
                return 'Unable to connect to database server. Please check if MySQL is running and the host/port are correct.';

            case 2003: // Can't connect to server
                return 'Cannot connect to MySQL server. Please verify the database host and port settings.';

            case 1044: // Access denied to database
                return 'Database user does not have permission to access databases. Please check user privileges.';

            default:
                if (strpos($errorMessage, 'Access denied') !== false) {
                    return 'Database access denied. Please check your username and password.';
                }

                if (strpos($errorMessage, 'Connection refused') !== false) {
                    return 'Database server connection refused. Please check if MySQL is running.';
                }

                return 'Database connection failed: Invalid credentials or server not accessible.';
        }
    }

    /**
     * Get user-friendly error message for database-specific issues
     */
    private function _getDatabaseErrorMessage(\PDOException $e, string $database): string
    {
        $errorCode = $e->getCode();
        $errorMessage = $e->getMessage();

        // Check for specific MySQL error codes
        switch ($errorCode) {
            case 1049: // Unknown database
                return "Database '{$database}' does not exist. Please create the database first or check the database name.";

            case 1044: // Access denied to database
                return "User does not have permission to access database '{$database}'. Please check database privileges.";

            default:
                if (strpos($errorMessage, 'Unknown database') !== false) {
                    return "Database '{$database}' was not found. Please create it first or verify the name.";
                }

                if (strpos($errorMessage, "doesn't exist") !== false) {
                    return "Database '{$database}' does not exist on the server. Please create it first.";
                }

                return "Cannot access database '{$database}'. Please check if it exists and you have proper permissions.";
        }
    }



    private  function _isDbEmpty() :bool {

        try {
            \Log::info('Checking if database is empty');
            
            // Use Laravel's DB connection instead of manual mysqli
            DB::connection()->getPdo(); // Test connection first
            
            $tables = DB::select('SHOW TABLES');
            $tableCount = count($tables);
            
            \Log::info('Database empty check result', [
                'table_count' => $tableCount,
                'is_empty' => $tableCount === 0
            ]);
            
            // If no tables exist, database is empty
            return $tableCount === 0;

        } catch (\Exception $e) {
            // If we can't check, assume it's not empty for safety
            \Log::error('Database empty check failed: ' . $e->getMessage());
            return false;
        }
    }



    private  function _envConfig(Request $request) :mixed {


        try {


            $key = base64_encode(random_bytes(32));
            $appName = config('installer.app_name');

            // Detect HTTPS properly from multiple sources
            $isHttps = $request->isSecure()
                || $request->server('HTTPS') === 'on'
                || $request->server('HTTP_X_FORWARDED_PROTO') === 'https'
                || $request->server('HTTP_X_FORWARDED_SSL') === 'on';

            // Get the base URL with proper protocol
            $protocol = $isHttps ? 'https://' : 'http://';
            $host = $request->getHost();
            $appUrl = $protocol . $host;

            // Add port if it's not standard (80 for HTTP, 443 for HTTPS)
            $port = $request->getPort();
            if (($isHttps && $port != 443) || (!$isHttps && $port != 80)) {
                $appUrl .= ':' . $port;
            }

            \Log::info('Setting APP_URL during installation', [
                'app_url' => $appUrl,
                'is_https' => $isHttps,
                'host' => $host,
                'port' => $port,
                'server_https' => $request->server('HTTPS'),
                'forwarded_proto' => $request->server('HTTP_X_FORWARDED_PROTO')
            ]);

            $output =
                'APP_NAME=' . $appName . PHP_EOL .
                'APP_ENV=live' . PHP_EOL .
                'APP_KEY=base64:' . $key . PHP_EOL .
                'APP_DEBUG=false' . PHP_EOL .
                'APP_INSTALL=false' . PHP_EOL .
                'APP_LOG_LEVEL=debug' . PHP_EOL .
                'APP_MODE=live' . PHP_EOL .
                'APP_URL=' . $appUrl . PHP_EOL .
                'ASSET_URL=' . $appUrl . PHP_EOL .

                'DB_CONNECTION=mysql' . PHP_EOL .
                'DB_HOST=' . $request->input("db_host") . PHP_EOL .
                'DB_PORT=' . $request->input("db_port") . PHP_EOL .
                'DB_DATABASE=' . $request->input("db_name") . PHP_EOL .
                'DB_USERNAME=' . $request->input("db_user") . PHP_EOL .
                'DB_PASSWORD=' . $request->input("db_password") . PHP_EOL .

                'BROADCAST_DRIVER=log' . PHP_EOL .
                'CACHE_DRIVER=file' . PHP_EOL .
                'CACHE_STORE=file' . PHP_EOL .
                'SESSION_DRIVER=file' . PHP_EOL .
                'SESSION_LIFETIME=120' . PHP_EOL .
                'SESSION_SECURE_COOKIE=' . ($isHttps ? 'true' : 'false') . PHP_EOL .
                'SESSION_SAME_SITE=lax' . PHP_EOL .
                'QUEUE_DRIVER=sync' . PHP_EOL .

                'REDIS_HOST=127.0.0.1' . PHP_EOL .
                'REDIS_PASSWORD=null' . PHP_EOL .
                'REDIS_PORT=6379' . PHP_EOL .

                'PUSHER_APP_ID=' . PHP_EOL .
                'PUSHER_APP_KEY=' . PHP_EOL .
                'PUSHER_APP_SECRET=' . PHP_EOL .
                'PUSHER_APP_CLUSTER=mt1' . PHP_EOL.
                'PURCHASE_KEY=' . PHP_EOL.
                'ENVATO_USERNAME=' . PHP_EOL;

            $file = fopen(base_path('.env'), 'w');
            fwrite($file, $output);
            fclose($file);

            $path = base_path('.env');
            if (file_exists($path)) {
                return true;
            }

            return false;

        } catch (\Throwable $th) {

            return false;
        }



    }

    private function _dbMigrate(mixed $forceImport) :void{

        ini_set('max_execution_time', 0);
        
        \Log::info('Starting database migration', ['force_import' => $forceImport]);
        
        if($forceImport == StatusEnum::true->status()){
            \Log::info('Force installation: wiping database completely');
            // Force installation: completely wipe and recreate database
            try {
                // Drop all tables first
                \Log::info('Executing db:wipe command');
                $wipExitCode = Artisan::call('db:wipe', ['--force' => true]);
                \Log::info('db:wipe completed', ['exit_code' => $wipExitCode]);
                
                // Run fresh migrations
                \Log::info('Executing migrate:fresh command');
                $migrateExitCode = Artisan::call('migrate:fresh', ['--force' => true]);
                \Log::info('migrate:fresh completed', ['exit_code' => $migrateExitCode]);
                
            } catch (\Exception $e) {
                \Log::warning('Standard wipe failed, trying manual cleanup', ['error' => $e->getMessage()]);
                // If standard commands fail, try manual database cleanup
                $this->_forceCleanDatabase();
                
                \Log::info('Executing migrate command after manual cleanup');
                $migrateExitCode = Artisan::call('migrate', ['--force' => true]);
                \Log::info('migrate completed after cleanup', ['exit_code' => $migrateExitCode]);
            }
        } else {
            \Log::info('Regular installation: running migrate:fresh');
            // Regular installation: just run migrations
            $migrateExitCode = Artisan::call('migrate:fresh', ['--force' => true]);
            \Log::info('migrate:fresh completed', ['exit_code' => $migrateExitCode]);
        }
        
        \Log::info('Database migration process completed');
    }

    /**
     * Force clean database by dropping all tables manually
     */
    private function _forceCleanDatabase() :void {
        try {
            \Log::info('Starting manual database cleanup');
            
            $tables = DB::select('SHOW TABLES');
            $databaseName = DB::getDatabaseName();
            $tableColumn = 'Tables_in_' . $databaseName;
            
            \Log::info('Found tables to drop', ['table_count' => count($tables), 'database' => $databaseName]);
            
            // Disable foreign key checks
            \Log::info('Disabling foreign key checks');
            DB::statement('SET FOREIGN_KEY_CHECKS=0');
            
            // Drop all tables
            $droppedTables = [];
            foreach ($tables as $table) {
                $tableName = $table->$tableColumn;
                \Log::info('Dropping table', ['table' => $tableName]);
                DB::statement("DROP TABLE IF EXISTS `{$tableName}`");
                $droppedTables[] = $tableName;
            }
            
            // Re-enable foreign key checks
            \Log::info('Re-enabling foreign key checks');
            DB::statement('SET FOREIGN_KEY_CHECKS=1');
            
            \Log::info('Manual database cleanup completed', ['dropped_tables' => $droppedTables]);
            
        } catch (\Exception $e) {
            // If manual cleanup fails, log it but continue
            \Log::error('Force database cleanup failed: ' . $e->getMessage());
            throw $e; // Re-throw to ensure calling code knows cleanup failed
        }
    }
    private function _dbSeed() :void{
        ini_set('max_execution_time', 0);
        
        \Log::info('Starting database seeding');
        $seedExitCode = Artisan::call('db:seed', ['--force' => true]);
        \Log::info('Database seeding completed', ['exit_code' => $seedExitCode]);
        
        if ($seedExitCode !== 0) {
            \Log::error('Database seeding failed with non-zero exit code', ['exit_code' => $seedExitCode]);
            throw new \Exception('Database seeding failed with exit code: ' . $seedExitCode);
        }
    }


    private function _systemInstalled(?string $purchaseKey = null, ?string $envatoUsername = null) :void {

        $this->_updateSetting();

        $message ="INSTALLED_AT:".Carbon::now();
        $logFile = storage_path(base64_decode(config('installer.cacheFile')));

        if (file_exists($logFile)) {
            unlink($logFile);
        }
        file_put_contents($logFile, $message);

        optimize_clear();

        Cache::remember('software_license',now()->addHour(),function ()  {
            return true;
        });
    }


    private function _updateSetting() :void {

        $data = [
            ['key' => "app_version", 'value' => Arr::get(config("installer.core"),'appVersion',null)],
            ['key' => "system_installed_at", 'value'    => Carbon::now()],
            ['key' => "is_domain_verified", 'value'     => StatusEnum::true->status()],
            ['key' => "next_verification", 'value'      => Carbon::now()->addDays()],
            
            // Professional Theme Colors - Modern Blue Theme
            ['key' => "primary_color", 'value' => "#667eea"],
            ['key' => "secondary_color", 'value' => "#764ba2"],
            ['key' => "primary_color_text", 'value' => "#ffffff"],
            ['key' => "secondary_color_text", 'value' => "#ffffff"],
            ['key' => "body_text_primary", 'value' => "#1a202c"],
            ['key' => "body_text_secondary", 'value' => "#718096"],
            
            // Other professional defaults
            ['key' => "site_name", 'value' => "Postuno"],
            ['key' => "data_per_page", 'value' => "15"],
        ];

        foreach ($data as $item) {
            Setting::updateOrInsert(['key' => $item['key']], $item);
        }

    }


}
