<?php

declare(strict_types=1);

namespace Instinctor\Core;


if (!defined('ABSPATH')) exit;
use Instinctor\Core\Storage\DriverInterface;
use Instinctor\Core\Storage\WordPressDriver;
use Instinctor\Core\Storage\LocalDriver;
use Instinctor\Core\Storage\S3Driver;

/**
 * Storage — static facade over the active storage driver.
 *
 * Driver selection:
 *   INSTINCTOR_ENV = 'wordpress'   → WordPressDriver  (default)
 *   INSTINCTOR_ENV = 'standalone'  → LocalDriver
 *   INSTINCTOR_ENV = 's3'          → S3Driver  (stub until M11)
 *
 * File paths in the database are always stored as relative paths
 * (e.g. "impulse-previews/page-42.jpg"). Use Storage::url($rel) to
 * resolve to a public URL and Storage::path($rel) for absolute paths.
 *
 * MIME validation:
 *   Storage::validate($filename, $data) checks extension + magic bytes.
 *   Storage::allowedTypes()            returns the default whitelist.
 *
 * Usage:
 *   $rel = 'impulse-previews/page-' . $id . '.jpg';
 *   Storage::put($rel, $binary);
 *   $url = Storage::url($rel);
 */
class Storage
{
    private static ?DriverInterface $driver = null;

    // -------------------------------------------------------------------------
    // Driver access
    // -------------------------------------------------------------------------

    public static function driver(): DriverInterface
    {
        if (self::$driver === null) {
            self::$driver = self::makeDriver();
        }
        return self::$driver;
    }

    /** Force a specific driver instance (useful for testing). */
    public static function setDriver(DriverInterface $driver): void
    {
        self::$driver = $driver;
    }

    private static function makeDriver(): DriverInterface
    {
        $env = defined('INSTINCTOR_ENV') ? INSTINCTOR_ENV : 'wordpress';

        return match ($env) {
            'standalone' => new LocalDriver(),
            's3'         => new S3Driver(),
            default      => new WordPressDriver(),
        };
    }

    // -------------------------------------------------------------------------
    // Path / URL resolution
    // -------------------------------------------------------------------------

    /**
     * Absolute path to the storage root (or an optional subdirectory).
     * e.g. Storage::basedir('instinctor-chat') → /var/www/html/wp-content/uploads/instinctor-chat
     */
    public static function basedir(string $sub = ''): string
    {
        return self::driver()->basedir($sub);
    }

    /**
     * Public URL to the storage root (or an optional subdirectory).
     */
    public static function baseurl(string $sub = ''): string
    {
        return self::driver()->baseurl($sub);
    }

    /**
     * Absolute path to a file stored at the given relative path.
     */
    public static function path(string $rel): string
    {
        return self::driver()->path($rel);
    }

    /**
     * Public URL for a file stored at the given relative path.
     */
    public static function url(string $rel): string
    {
        return self::driver()->url($rel);
    }

    // -------------------------------------------------------------------------
    // File operations
    // -------------------------------------------------------------------------

    /**
     * Create a directory relative to the storage root.
     */
    public static function mkdir(string $rel): bool
    {
        return self::driver()->mkdir($rel);
    }

    /**
     * Write raw data to a file. Creates parent directories if needed.
     */
    public static function put(string $rel, string $data): bool
    {
        return self::driver()->put($rel, $data);
    }

    /**
     * Read file contents. Returns false if the file does not exist.
     */
    public static function get(string $rel): string|false
    {
        return self::driver()->get($rel);
    }

    /**
     * Delete a file.
     */
    public static function delete(string $rel): bool
    {
        return self::driver()->delete($rel);
    }

    /**
     * Check whether a file exists.
     */
    public static function exists(string $rel): bool
    {
        return self::driver()->exists($rel);
    }

    /**
     * File size in bytes. Returns 0 if not found.
     */
    public static function size(string $rel): int
    {
        return self::driver()->size($rel);
    }

    /**
     * Detect MIME type of a file. Accepts absolute or relative paths.
     */
    public static function mime(string $path): string
    {
        return self::driver()->mime($path);
    }

    // -------------------------------------------------------------------------
    // MIME validation
    // -------------------------------------------------------------------------

    /**
     * Default allowed MIME types for user-uploaded files.
     *
     * @return array<string, string[]>  Keys are MIME types, values are allowed extensions.
     */
    public static function allowedTypes(): array
    {
        return [
            'image/jpeg'      => ['jpg', 'jpeg'],
            'image/png'       => ['png'],
            'image/gif'       => ['gif'],
            'image/webp'      => ['webp'],
            
            'application/pdf' => ['pdf'],
            'application/zip' => ['zip'],
            'text/plain'      => ['txt'],
            'video/mp4'       => ['mp4'],
            'audio/mpeg'      => ['mp3'],
        ];
    }

    /**
     * Validate that a filename's extension is in the allowed list.
     *
     * @param string   $filename     Original filename (used for extension check only).
     * @param string[] $allowedMimes Optional subset of MIME types to allow.
     *                               Defaults to all types in allowedTypes().
     */
    public static function validate(string $filename, array $allowedMimes = []): bool
    {
        $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION));

        if (empty($ext)) {
            return false;
        }

        $allowedTypes = self::allowedTypes();

        if (!empty($allowedMimes)) {
            $allowedTypes = array_intersect_key($allowedTypes, array_flip($allowedMimes));
        }

        foreach ($allowedTypes as $extensions) {
            if (in_array($ext, $extensions, true)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Validate a filename and also verify the file's actual MIME type
     * matches its extension (magic bytes check via stored absolute path).
     *
     * @param string   $filename  Original filename.
     * @param string   $absPath   Absolute path to the uploaded file for MIME detection.
     * @param string[] $allowedMimes  Optional subset of MIME types.
     */
    public static function validateFile(string $filename, string $absPath, array $allowedMimes = []): bool
    {
        if (!self::validate($filename, $allowedMimes)) {
            return false;
        }

        $detectedMime = mime_content_type($absPath);
        if ($detectedMime === false) {
            return false;
        }

        $allowedTypes = self::allowedTypes();
        if (!empty($allowedMimes)) {
            $allowedTypes = array_intersect_key($allowedTypes, array_flip($allowedMimes));
        }

        return array_key_exists($detectedMime, $allowedTypes);
    }
}
