<?php

namespace app\models;

use yii\base\Model;
use ApkParser\Parser;

class ApkFile extends Model
{
    public $filename;
    public $filepath;
    public $packageName;
    public $versionCode;
    public $versionName;
    public $minSdkVersion;
    public $targetSdkVersion;
    public $permissions = [];
    public $applicationLabel;
    public $fileSize = 0;
    public $installedSize = 0;

    private $isParseSuccess = false;
    private $parseError = null;

    public function rules()
    {
        return [
            [['filename', 'filepath'], 'required'],
            [['packageName', 'versionCode', 'versionName'], 'string'],
            [['minSdkVersion', 'targetSdkVersion'], 'integer'],
            [['permissions'], 'safe'],
        ];
    }

    public function __construct(string $filename, string $filepath)
    {
        $this->filename = $filename;
        $this->filepath = $filepath;
    }

    /**
     * Verifa se o processo de _parsing_ executou corretamente
     * 
     * @return bool 
     */
    public function checkIsValid(): bool
    {
        return $this->isParseSuccess;
    }

    /**
     * Get parse error message if any
     * 
     * @return string|null
     */
    public function getParseError(): ?string
    {
        return $this->parseError;
    }

    /**
     * Get JSON cache file path
     * 
     * @return string
     */
    private function getCacheFilePath(): string
    {
        return $this->filepath . '.json';
    }

    /**
     * Load APK metadata from JSON cache or parse APK file
     * 
     * @return bool Success status
     */
    public function loadMetadata(): bool
    {
        $cacheFile = $this->getCacheFilePath();

        // Check if cache exists and is readable
        if (file_exists($cacheFile) && is_readable($cacheFile)) {
            if ($this->loadFromCache($cacheFile)) {
                return true;
            }
            // Cache invalid/corrupt, fall through to parse
        }

        // Parse APK and save cache
        if ($this->parseApkFile()) {
            $this->saveToCache($cacheFile);
            return true;
        }

        return false;
    }

    /**
     * Load metadata from JSON cache file
     * 
     * @param string $cacheFile Path to cache file
     * @return bool Success status
     */
    private function loadFromCache(string $cacheFile): bool
    {
        try {
            $json = file_get_contents($cacheFile);
            if ($json === false) {
                return false;
            }

            $data = json_decode($json, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                $this->parseError = "JSON decode error: " . json_last_error_msg();
                return false;
            }

            // Validate required fields
            if (!isset($data['packageName'])) {
                $this->parseError = "Invalid cache: missing required fields";
                return false;
            }

            // Populate properties
            $this->packageName = $data['packageName'] ?? 'Unknown';
            $this->versionCode = $data['versionCode'] ?? null;
            $this->versionName = $data['versionName'] ?? null;
            $this->minSdkVersion = $data['minSdkVersion'] ?? null;
            $this->targetSdkVersion = $data['targetSdkVersion'] ?? null;
            $this->applicationLabel = $data['applicationLabel'] ?? null;
            $this->permissions = $data['permissions'] ?? [];
            $this->fileSize = filesize($this->filepath) ?: 0;
            $this->installedSize = $data['installedSize'] ?? 0;

            $this->isParseSuccess = true;
            return true;

        } catch (\Exception $e) {
            $this->parseError = "Cache load failed: " . $e->getMessage();
            return false;
        }
    }

    /**
     * Save metadata to JSON cache file
     * 
     * @param string $cacheFile Path to cache file
     * @return bool Success status
     */
    private function saveToCache(string $cacheFile): bool
    {
        try {
            $data = [
                'packageName' => $this->packageName,
                'versionCode' => $this->versionCode,
                'versionName' => $this->versionName,
                'minSdkVersion' => $this->minSdkVersion,
                'targetSdkVersion' => $this->targetSdkVersion,
                'applicationLabel' => $this->applicationLabel,
                'permissions' => $this->permissions,
                'fileSize' => $this->fileSize,
                'installedSize' => $this->installedSize,
                'cachedAt' => date('Y-m-d H:i:s'),
            ];

            $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
            if ($json === false) {
                return false;
            }

            return file_put_contents($cacheFile, $json, LOCK_EX) !== false;

        } catch (\Exception $e) {
            // Non-critical error, already have parsed data
            return false;
        }
    }

    /**
     * Parse APK file to extract comprehensive metadata
     * Uses tufanbarisyildirim/php-apk-parser
     * 
     * @return bool Success status
     */
    public function parseApkFile(): bool
    {
        if (!file_exists($this->filepath)) {
            $this->parseError = "APK file not found: {$this->filepath}";
            return false;
        }

        try {
            $apk = new Parser($this->filepath, ['manifest_only' => false]);

            // Extract manifest data
            $manifest = $apk->getManifest();

            // Package information
            $this->packageName = $manifest->getPackageName() ?? 'Unknown';
            $this->versionCode = $manifest->getVersionCode() ?? null;
            $this->versionName = $manifest->getVersionName() ?? null;

            // SDK versions
            $this->minSdkVersion = $manifest->getMinSdkLevel() ?? null;
            $this->targetSdkVersion = $manifest->getTargetSdkLevel() ?? null;

            // Application label
            $labelResourceId = $manifest->getApplication()->getLabel();
            $this->applicationLabel = $apk->getResources($labelResourceId)[0];

            // Permissions
            $this->permissions = $manifest->getPermissions() ?? [];

            // File and App size
            $this->fileSize = filesize($this->filepath) ?: 0;
            $this->installedSize = $this->calculateInstalledSize();

            $this->isParseSuccess = true;
            return true;

        } catch (\Exception $e) {
            $this->parseError = "APK parsing failed: " . $e->getMessage();
            $this->isParseSuccess = false;
            return false;
        }
    }

    /**
     * Calculates the precise installed size of an APK by summing the
     * uncompressed sizes of every entry in the ZIP archive.
     *
     * APKs are standard ZIP archives. The uncompressed entry sizes represent
     * exactly how many bytes the package manager will expand on-device,
     * making this a more accurate "installed size" than the file-on-disk size.
     *
     * Falls back to 0 if ZipArchive is unavailable or the archive is corrupt.
     *
     * @return int Total uncompressed bytes across all ZIP entries.
     */
    private function calculateInstalledSize(): int
    {
        if (!class_exists('\ZipArchive')) {
            return 0;
        }

        $zip = new \ZipArchive();

        if ($zip->open($this->filepath) !== true) {
            return 0;
        }

        $total = 0;
        for ($i = 0; $i < $zip->numFiles; $i++) {
            $stat = $zip->statIndex($i);
            $total += $stat['size'] ?? 0;   // 'size' = uncompressed; 'comp_size' = compressed
        }

        $zip->close();

        return $total;
    }
}
