<?php

namespace FileRise\WebDAV;

use FileRise\Support\ACL;
use FileRise\Support\AuditHook;
use FileRise\Support\UploadNamePolicy;
use FileRise\Domain\FileModel;
use FileRise\Domain\FolderCrypto;
use FileRise\WebDAV\CurrentUser;
use Sabre\DAV\IFile;
use Sabre\DAV\INode;
use Sabre\DAV\Exception\Forbidden;

// src/webdav/FileRiseFile.php


require_once dirname(__DIR__, 3) . '/config/config.php';
require_once dirname(__DIR__, 3) . '/vendor/autoload.php';
require_once dirname(__DIR__, 3) . '/src/lib/ACL.php';
require_once dirname(__DIR__, 3) . '/src/lib/AuditHook.php';
require_once __DIR__ . '/CurrentUser.php';


class FileRiseFile implements IFile, INode
{
    private string $path;
    private string $user;
    private bool $isAdmin;
    private array $perms;

    public function __construct(string $path, string $user, bool $isAdmin, array $perms)
    {
        $this->path    = $path;
        $this->user    = $user;
        $this->isAdmin = $isAdmin;
        $this->perms   = $perms;
    }

    // ── INode ───────────────────────────────────────────

    public function getName(): string
    {
        return basename($this->path);
    }

    public function getLastModified(): int
    {
        return @filemtime($this->path) ?: time();
    }

    public function delete(): void
    {
        [$folderKey, $fileName] = $this->split();

        if (FolderCrypto::isEncryptedOrAncestor($folderKey)) {
            throw new Forbidden('WebDAV is disabled inside encrypted folders');
        }

        if (!$this->isAdmin && !ACL::canDelete($this->user, $this->perms, $folderKey)) {
            throw new Forbidden('No delete permission in this folder');
        }
        if (!$this->canTouchOwnership($folderKey, $fileName)) {
            throw new Forbidden('You do not own this file');
        }
        $result = FileModel::deleteFiles($folderKey, [$fileName]);
        if (is_array($result) && isset($result['success'])) {
            \AuditHook::log('file.delete', [
                'user'   => $this->user,
                'source' => 'webdav',
                'folder' => $folderKey,
                'path'   => ($folderKey === 'root') ? $fileName : ($folderKey . '/' . $fileName),
            ]);
        }
    }

    public function setName($newName): void
    {
        throw new Forbidden('Renaming files via WebDAV is not supported');
    }

    // ── IFile ───────────────────────────────────────────

    public function get()
    {
        [$folderKey, $fileName] = $this->split();
        if (FolderCrypto::isEncryptedOrAncestor($folderKey)) {
            throw new Forbidden('WebDAV is disabled inside encrypted folders');
        }
        $canFull = $this->isAdmin || ACL::canRead($this->user, $this->perms, $folderKey);
        if (!$canFull) {
            // own-only?
            if (!ACL::hasGrant($this->user, $folderKey, 'read_own') || !$this->isOwner($folderKey, $fileName)) {
                throw new Forbidden('No view access to this file');
            }
        }
        \AuditHook::log('file.download', [
            'user'   => $this->user,
            'source' => 'webdav',
            'folder' => $folderKey,
            'path'   => ($folderKey === 'root') ? $fileName : ($folderKey . '/' . $fileName),
        ]);
        return fopen($this->path, 'rb');
    }

    public function put($data): ?string
    {
        [$folderKey, $fileName] = $this->split();
        $fileName = basename(trim($fileName));

        if (FolderCrypto::isEncryptedOrAncestor($folderKey)) {
            throw new Forbidden('WebDAV is disabled inside encrypted folders');
        }
        if (!self::isAllowedWebDavFileName($fileName)) {
            throw new Forbidden('Invalid file name.');
        }

        $exists = is_file($this->path);

        if (!$this->isAdmin) {
            // uploads disabled blocks both create & overwrite
            if (!empty($this->perms['disableUpload'])) {
                throw new Forbidden('Uploads are disabled for your account');
            }
            // granular gates
            if ($exists) {
                if (!ACL::canEdit($this->user, $this->perms, $folderKey)) {
                    throw new Forbidden('No edit permission in this folder');
                }
            } else {
                if (!ACL::canUpload($this->user, $this->perms, $folderKey)) {
                    throw new Forbidden('No upload permission in this folder');
                }
            }
        }

        // Ownership on overwrite (unless admin/bypass)
        $bypass = !empty($this->perms['bypassOwnership']) || $this->isAdmin;
        if ($exists && !$bypass && !$this->isOwner($folderKey, $fileName)) {
            throw new Forbidden('You do not own the target file');
        }

        // write + metadata (unchanged)
        $maxBytes = defined('FR_WEBDAV_MAX_UPLOAD_BYTES') ? (int)FR_WEBDAV_MAX_UPLOAD_BYTES : 0;
        if ($maxBytes < 0) {
            $maxBytes = 0;
        }

        if (is_resource($data)) {
            $dir = dirname($this->path);
            $tmp = $dir . DIRECTORY_SEPARATOR . '.' . basename($this->path) . '.tmp-' . bin2hex(random_bytes(8));
            $out = @fopen($tmp, 'wb');
            if ($out === false) {
                throw new Forbidden('Unable to write file');
            }
            $written = 0;
            while (!feof($data)) {
                $chunk = fread($data, 1024 * 1024);
                if ($chunk === false) {
                    fclose($out);
                    @unlink($tmp);
                    throw new Forbidden('Unable to write file');
                }
                if ($chunk === '') {
                    continue;
                }
                $len = strlen($chunk);
                if ($maxBytes > 0 && ($written + $len) > $maxBytes) {
                    fclose($out);
                    @unlink($tmp);
                    throw new Forbidden('Upload exceeds maximum size');
                }
                $offset = 0;
                while ($offset < $len) {
                    $wrote = fwrite($out, substr($chunk, $offset));
                    if ($wrote === false || $wrote === 0) {
                        fclose($out);
                        @unlink($tmp);
                        throw new Forbidden('Unable to write file');
                    }
                    $offset += $wrote;
                }
                $written += $len;
            }
            fclose($out);
            if (!@rename($tmp, $this->path)) {
                if ($exists) {
                    @unlink($this->path);
                }
                if (!@rename($tmp, $this->path)) {
                    @unlink($tmp);
                    throw new Forbidden('Unable to write file');
                }
            }
        } else {
            $payload = (string)$data;
            if ($maxBytes > 0 && strlen($payload) > $maxBytes) {
                throw new Forbidden('Upload exceeds maximum size');
            }
            file_put_contents($this->path, $payload);
        }
        $this->updateMetadata($folderKey, $fileName);
        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }
        \AuditHook::log($exists ? 'file.edit' : 'file.upload', [
            'user'   => $this->user,
            'source' => 'webdav',
            'folder' => $folderKey,
            'path'   => ($folderKey === 'root') ? $fileName : ($folderKey . '/' . $fileName),
        ]);
        return null;
    }

    public function getSize(): int
    {
        return @filesize($this->path) ?: 0;
    }

    public function getETag(): string
    {
        return '"' . md5(($this->getLastModified() ?: 0) . ':' . ($this->getSize() ?: 0)) . '"';
    }

    public function getContentType(): ?string
    {
        return @mime_content_type($this->path) ?: null;
    }

    // ── helpers ──────────────────────────────────────────────────────────────

    private function split(): array
    {
        $base   = rtrim(UPLOAD_DIR, '/\\') . DIRECTORY_SEPARATOR;
        $rel    = ltrim(str_replace('\\', '/', substr($this->path, strlen($base))), '/');
        $parts  = explode('/', $rel);
        $file   = array_pop($parts);
        $folder = empty($parts) ? 'root' : implode('/', $parts);
        return [$folder, $file];
    }

    private function metaFile(string $folderKey): string
    {
        return META_DIR . (
            $folderKey === 'root'
                ? 'root_metadata.json'
                : str_replace(['/', '\\', ' '], '-', $folderKey) . '_metadata.json'
        );
    }

    private function loadMeta(string $folderKey): array
    {
        $mf = $this->metaFile($folderKey);
        if (!is_file($mf)) {
            return [];
        }
        $d = json_decode(@file_get_contents($mf), true);
        return is_array($d) ? $d : [];
    }

    private function saveMeta(string $folderKey, array $meta): void
    {
        @file_put_contents($this->metaFile($folderKey), json_encode($meta, JSON_PRETTY_PRINT));
    }

    private function isOwner(string $folderKey, string $fileName): bool
    {
        $meta = $this->loadMeta($folderKey);
        return isset($meta[$fileName]['uploader']) &&
               strcasecmp((string)$meta[$fileName]['uploader'], $this->user) === 0;
    }

    private function canTouchOwnership(string $folderKey, string $fileName): bool
    {
        if ($this->isAdmin || !empty($this->perms['bypassOwnership'])) {
            return true;
        }
        return $this->isOwner($folderKey, $fileName);
    }

    private function updateMetadata(string $folderKey, string $fileName): void
    {
        $meta     = $this->loadMeta($folderKey);
        $now      = date(DATE_TIME_FORMAT);
        $uploaded = $meta[$fileName]['uploaded'] ?? $now;
        $uploader = CurrentUser::get() ?: $this->user;

        $meta[$fileName] = [
            'uploaded' => $uploaded,
            'modified' => $now,
            'uploader' => $uploader,
        ];
        $this->saveMeta($folderKey, $meta);
    }

    public static function isAllowedWebDavFileName(string $fileName): bool
    {
        return UploadNamePolicy::isAllowedForWrite($fileName);
    }
}
