<?php

namespace PortalPeople\NgaugeBackupSql;

use Yii;
use yii\base\BootstrapInterface;
use yii\console\Application as ConsoleApp;

/**
 * NgaugeBackupSql Module
 * 
 * Database table backup and restore module with compression and versioning.
 * 
 * @property string $backupPath Path to backup storage directory
 * @property int $retentionDays Number of days to retain backups
 * @property bool $compressBackups Whether to compress backups with gzip
 * @property array $excludeTables Tables to exclude from backup listings
 * @property string $dbComponent Name of the database component to use
 */
class Module extends \yii\base\Module implements BootstrapInterface
{
    /**
     * @var string Namespace for controllers
     */
    public $controllerNamespace = 'PortalPeople\NgaugeBackupSql\controllers';
    
    /**
     * @var string Path to backup storage directory (Yii alias or absolute path)
     */
    public $backupPath = '@backups/sql';
    
    /**
     * @var int Number of days to retain backups (0 = keep forever)
     */
    public $retentionDays = 30;
    
    /**
     * @var bool Whether to compress backups with gzip
     */
    public $compressBackups = true;
    
    /**
     * @var array Tables to exclude from backup listings
     */
    public $excludeTables = ['log', 'queue', 'session'];
    
    /**
     * @var string Name of the database component to use
     */
    public $dbComponent = 'db';
    
    /**
     * @var int Maximum file size in bytes for single backup (0 = unlimited)
     */
    public $maxBackupSize = 0;
    
    /**
     * @var string|null Path to mysqldump/mariadb-dump binary (null = auto-detect)
     */
    public $mysqldumpPath = null;
    
    /**
     * @var string|null Path to mysql/mariadb binary (null = auto-detect)
     */
    public $mysqlPath = null;
    
    /**
     * @var bool Enable encryption for backups
     */
    public $encryptionEnabled = false;
    
    /**
     * @var string|null Encryption key (base64: prefix for base64 encoded keys)
     * If null, system-specific key will be generated
     */
    public $encryptionKey = null;
    
    /**
     * @var string|null Custom system identifier for encryption
     * If null, will use hostname and server name
     */
    public $systemId = null;
    
    /**
     * @var array List of authorized system identifiers that can decrypt backups
     * Empty array means only the creating system can decrypt
     */
    public $authorizedSystems = [];
    
    /**
     * @var string|null URL to fetch master encryption key from
     * If set, the encryption key will be fetched from this URL instead of local config
     */
    public $masterKeyUrl = null;
    
    /**
     * @var array List of allowed IP addresses that can use this module
     * Empty array means all IPs are allowed
     */
    public $allowedIps = [];

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();
        
        // Check IP access control
        if (!empty($this->allowedIps) && !$this->isIpAllowed()) {
            throw new \yii\web\ForbiddenHttpException('Access denied: IP address not authorized for backup operations.');
        }
        
        // Check for master key URL first (highest priority)
        if (!empty($this->masterKeyUrl)) {
            $this->encryptionKey = $this->fetchMasterKey($this->masterKeyUrl);
        }
        // Then check environment variable
        else {
            $envEncryptionKey = getenv('DB_BACKUP_ENCRYPTION_KEY');
            if ($envEncryptionKey !== false) {
                $this->encryptionKey = $envEncryptionKey;
            }
        }
        
        // Load configuration from backup.php if it exists
        $configFile = null;
        
        // Try @common/config first (Yii2 advanced template)
        try {
            $commonConfigFile = Yii::getAlias('@common/config/backup.php');
            if (file_exists($commonConfigFile)) {
                $configFile = $commonConfigFile;
            }
        } catch (\Exception $e) {
            // @common alias doesn't exist
        }
        
        // Try @app/config (console, backend, frontend specific config)
        if (!$configFile) {
            try {
                $appConfigFile = Yii::getAlias('@app/config/backup.php');
                if (file_exists($appConfigFile)) {
                    $configFile = $appConfigFile;
                }
            } catch (\Exception $e) {
                // @app alias doesn't exist
            }
        }
        
        if ($configFile !== null && file_exists($configFile)) {
            $config = require $configFile;
            
            // Apply configuration properties (but don't override master key or env variable)
            if (is_array($config)) {
                foreach ($config as $key => $value) {
                    if ($key !== 'class' && property_exists($this, $key)) {
                        // Skip encryptionKey if already set from master key URL or environment
                        if ($key === 'encryptionKey' && !empty($this->encryptionKey)) {
                            continue;
                        }
                        $this->$key = $value;
                    }
                }
            }
        }
        
        // Set default module ID if not set
        if ($this->id === null) {
            $this->id = 'db-backup';
        }
        
        // Ensure backup directory exists
        $backupPath = Yii::getAlias($this->backupPath);
        if (!is_dir($backupPath)) {
            @mkdir($backupPath, 0755, true);
        }
        
    }

    /**
     * Bootstrap method to be called during application bootstrap stage
     * 
     * @param \yii\base\Application $app The application instance
     */
    public function bootstrap($app)
    {
        // Register module if not already registered
        if (!isset($app->getModules()[$this->id])) {
            $app->setModule($this->id, [
                'class' => 'PortalPeople\NgaugeBackupSql\Module',
                'backupPath' => $this->backupPath,
                'retentionDays' => $this->retentionDays,
                'compressBackups' => $this->compressBackups,
                'excludeTables' => $this->excludeTables,
                'dbComponent' => $this->dbComponent,
                'maxBackupSize' => $this->maxBackupSize,
                'mysqldumpPath' => $this->mysqldumpPath,
                'mysqlPath' => $this->mysqlPath,
            ]);
        }
        
        // Register console command for console applications
        if ($app instanceof ConsoleApp) {
            $app->controllerMap['db-backup'] = [
                'class' => 'PortalPeople\NgaugeBackupSql\BackupCommand',
                'module' => $app->getModule($this->id),
            ];
        }
        
        // Register Backup component alias
        Yii::$container->set('PortalPeople\NgaugeBackupSql\components\BackupManager', [
            'module' => $app->getModule($this->id),
        ]);

        \yii\base\Event::on(\backend\controllers\ProductsController::class, 'beforePricingImportApply', function ($event) {
           $file = \PortalPeople\NgaugeBackupSql\components\Backup::SQLTable('pricing', 'pricing_'.date('Ymd_Hi').'.sql.gz');
           Yii::warning("Created backup of 'Pricing' table before import: " . $file);
        });
    }
    
    /**
     * Get the resolved backup path
     * 
     * @return string Absolute path to backup directory
     */
    public function getBackupPath()
    {
        return Yii::getAlias($this->backupPath);
    }
    
    /**
     * Get the database component
     * 
     * @return \yii\db\Connection
     */
    public function getDb()
    {
        return Yii::$app->get($this->dbComponent);
    }
    
    /**
     * Check if current IP is allowed to use this module
     * 
     * @return bool
     */
    protected function isIpAllowed()
    {
        if (empty($this->allowedIps)) {
            return true; // No restrictions
        }
        
        $userIp = Yii::$app->request->userIP ?? $_SERVER['REMOTE_ADDR'] ?? null;
        
        if (!$userIp) {
            return false; // Cannot determine IP
        }
        
        // Check if IP is in allowed list (supports wildcards and CIDR)
        foreach ($this->allowedIps as $allowedIp) {
            if ($this->matchIp($userIp, $allowedIp)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * Match IP address against pattern (supports wildcards and CIDR)
     * 
     * @param string $ip IP address to check
     * @param string $pattern Pattern to match against
     * @return bool
     */
    protected function matchIp($ip, $pattern)
    {
        // Exact match
        if ($ip === $pattern) {
            return true;
        }
        
        // Wildcard match (e.g., 192.168.1.*)
        if (strpos($pattern, '*') !== false) {
            $pattern = str_replace('.', '\.', $pattern);
            $pattern = str_replace('*', '.*', $pattern);
            return preg_match('/^' . $pattern . '$/', $ip) === 1;
        }
        
        // CIDR notation (e.g., 192.168.1.0/24)
        if (strpos($pattern, '/') !== false) {
            list($subnet, $bits) = explode('/', $pattern);
            $ip_long = ip2long($ip);
            $subnet_long = ip2long($subnet);
            $mask = -1 << (32 - (int)$bits);
            $subnet_long &= $mask;
            return ($ip_long & $mask) === $subnet_long;
        }
        
        return false;
    }
    
    /**
     * Fetch master encryption key from remote URL
     * 
     * @param string $url URL to fetch key from
     * @return string|null Encryption key or null on failure
     */
    protected function fetchMasterKey($url)
    {
        try {
            $context = stream_context_create([
                'http' => [
                    'timeout' => 5, // 5 second timeout
                    'user_agent' => 'Yii2-Backup-Module/1.0',
                ],
            ]);
            
            $key = @file_get_contents($url, false, $context);
            
            if ($key === false) {
                Yii::error("Failed to fetch master key from: {$url}", __METHOD__);
                return null;
            }
            
            return trim($key);
            
        } catch (\Exception $e) {
            Yii::error("Exception fetching master key: " . $e->getMessage(), __METHOD__);
            return null;
        }
    }
}
