<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use OCA\Mitgliederverwaltung\Db\Lager;
use OCA\Mitgliederverwaltung\Db\LagerMapper;
use OCA\Mitgliederverwaltung\Db\Member;
use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IConfig;
use Psr\Log\LoggerInterface;

/**
 * Service for computing member/Lager folder paths, listing folder contents,
 * and optionally auto-creating member folders in Nextcloud Files.
 *
 * Configurable settings stored via IConfig (app values):
 * - file_base_path: base directory for member folders (default: /Verein/Mitglieder/)
 * - file_subfolder_pattern: pattern for member subfolder names (default: {Nachname}_{Vorname})
 * - file_auto_create: whether to auto-create folders (default: false)
 * - file_lager_base_path: base directory for Lager folders (default: /Verein/Lager/)
 *
 * Part of Issue #52.
 */
class FileLinkService {

    private const APP_ID = 'mitgliederverwaltung';

    private const DEFAULT_BASE_PATH = '/Verein/Mitglieder/';
    private const DEFAULT_SUBFOLDER_PATTERN = '{Nachname}_{Vorname}';
    private const DEFAULT_AUTO_CREATE = 'false';
    private const DEFAULT_LAGER_BASE_PATH = '/Verein/Lager/';

    private IRootFolder $rootFolder;
    private IConfig $config;
    private MemberMapper $memberMapper;
    private LagerMapper $lagerMapper;
    private LoggerInterface $logger;
    private ?string $userId;

    public function __construct(
        IRootFolder $rootFolder,
        IConfig $config,
        MemberMapper $memberMapper,
        LagerMapper $lagerMapper,
        LoggerInterface $logger,
        ?string $userId
    ) {
        $this->rootFolder = $rootFolder;
        $this->config = $config;
        $this->memberMapper = $memberMapper;
        $this->lagerMapper = $lagerMapper;
        $this->logger = $logger;
        $this->userId = $userId;
    }

    // ── Path computation ────────────────────────────────────────────

    /**
     * Compute the folder path for a member based on configured pattern.
     *
     * @param Member $member The member entity
     * @return string The computed folder path (e.g. /Verein/Mitglieder/Mueller_Max/)
     */
    public function getMemberFolderPath(Member $member): string {
        $basePath = rtrim($this->getBasePath(), '/');
        $pattern = $this->getSubfolderPattern();

        $subfolder = $this->applyPattern($pattern, $member);

        return $basePath . '/' . $subfolder;
    }

    /**
     * Compute the folder path for a Lager based on configured base path.
     *
     * @param Lager $lager The Lager entity
     * @return string The computed folder path (e.g. /Verein/Lager/Sommerlager_2026/)
     */
    public function getLagerFolderPath(Lager $lager): string {
        $basePath = rtrim($this->getLagerBasePath(), '/');
        $name = $this->sanitizeFolderName($lager->getName());

        return $basePath . '/' . $name;
    }

    // ── Folder operations ───────────────────────────────────────────

    /**
     * Ensure a member's folder exists. Creates it if auto-create is enabled or force is true.
     *
     * @param Member $member The member entity
     * @param bool $force Create the folder regardless of auto-create setting (for explicit user actions)
     * @return bool True if folder exists after this call
     */
    public function ensureMemberFolder(Member $member, bool $force = false): bool {
        if ($this->userId === null) {
            $this->logger->warning('Cannot ensure folder: no user context', [
                'app' => self::APP_ID,
            ]);
            return false;
        }

        $path = $this->getMemberFolderPath($member);

        try {
            $userFolder = $this->rootFolder->getUserFolder($this->userId);

            try {
                $userFolder->get($path);
                return true; // Folder already exists
            } catch (NotFoundException $e) {
                if ($force || $this->isAutoCreateEnabled()) {
                    $userFolder->newFolder($path);
                    $this->logger->info('Created member folder', [
                        'memberId' => $member->getId(),
                        'path' => $path,
                        'app' => self::APP_ID,
                    ]);
                    return true;
                }
                return false;
            }
        } catch (\Exception $e) {
            $this->logger->error('Failed to ensure member folder', [
                'memberId' => $member->getId(),
                'path' => $path,
                'exception' => $e,
                'app' => self::APP_ID,
            ]);
            return false;
        }
    }

    /**
     * List contents of a folder in the current user's file space.
     *
     * @param string $path The folder path relative to user root
     * @return array Array of file info: [name, size, mtime, mimetype, path]
     * @throws NotFoundException If the folder does not exist
     */
    public function listFolderContents(string $path): array {
        if ($this->userId === null) {
            throw new \RuntimeException('No user context for listing folder contents');
        }

        $userFolder = $this->rootFolder->getUserFolder($this->userId);
        $folder = $userFolder->get($path);

        if ($folder->getType() !== \OCP\Files\FileInfo::TYPE_FOLDER) {
            throw new \RuntimeException('Path is not a folder: ' . $path);
        }

        /** @var \OCP\Files\Folder $folder */
        $contents = $folder->getDirectoryListing();
        $files = [];

        foreach ($contents as $node) {
            $files[] = [
                'name' => $node->getName(),
                'size' => $node->getSize(),
                'mtime' => $node->getMTime(),
                'mimetype' => $node->getMimetype(),
                'path' => $node->getPath(),
                'type' => $node->getType() === \OCP\Files\FileInfo::TYPE_FOLDER ? 'folder' : 'file',
            ];
        }

        // Sort: folders first, then by name
        usort($files, function ($a, $b) {
            if ($a['type'] !== $b['type']) {
                return $a['type'] === 'folder' ? -1 : 1;
            }
            return strcasecmp($a['name'], $b['name']);
        });

        return $files;
    }

    /**
     * Convenience method: get files for a member's folder.
     *
     * @param Member $member The member entity
     * @return array{exists: bool, path: string, files: array} Folder status and contents
     */
    public function getMemberFiles(Member $member): array {
        $path = $this->getMemberFolderPath($member);

        try {
            $files = $this->listFolderContents($path);
            return [
                'exists' => true,
                'path' => $path,
                'files' => $files,
            ];
        } catch (NotFoundException $e) {
            return [
                'exists' => false,
                'path' => $path,
                'files' => [],
            ];
        } catch (\Exception $e) {
            $this->logger->error('Failed to list member files', [
                'memberId' => $member->getId(),
                'path' => $path,
                'exception' => $e,
                'app' => self::APP_ID,
            ]);
            return [
                'exists' => false,
                'path' => $path,
                'files' => [],
            ];
        }
    }

    /**
     * Get files for a member by ID.
     *
     * @param int $memberId The member ID
     * @return array{exists: bool, path: string, files: array}
     * @throws DoesNotExistException
     */
    public function getMemberFilesById(int $memberId): array {
        $member = $this->memberMapper->findById($memberId);
        return $this->getMemberFiles($member);
    }

    /**
     * Ensure member folder exists by member ID.
     *
     * @param int $memberId The member ID
     * @param bool $force Create the folder regardless of auto-create setting
     * @return array{exists: bool, path: string}
     * @throws DoesNotExistException
     */
    public function ensureMemberFolderById(int $memberId, bool $force = false): array {
        $member = $this->memberMapper->findById($memberId);
        $exists = $this->ensureMemberFolder($member, $force);
        return [
            'exists' => $exists,
            'path' => $this->getMemberFolderPath($member),
        ];
    }

    /**
     * Ensure a Lager's folder exists. Creates it if force is true or auto-create is enabled.
     *
     * @param Lager $lager The Lager entity
     * @param bool $force Create the folder regardless of auto-create setting
     * @return bool True if folder exists after this call
     */
    public function ensureLagerFolder(Lager $lager, bool $force = false): bool {
        if ($this->userId === null) {
            $this->logger->warning('Cannot ensure Lager folder: no user context', [
                'app' => self::APP_ID,
            ]);
            return false;
        }

        $path = $this->getLagerFolderPath($lager);

        try {
            $userFolder = $this->rootFolder->getUserFolder($this->userId);

            try {
                $userFolder->get($path);
                return true;
            } catch (NotFoundException $e) {
                if ($force || $this->isAutoCreateEnabled()) {
                    $userFolder->newFolder($path);
                    $this->logger->info('Created Lager folder', [
                        'lagerId' => $lager->getId(),
                        'path' => $path,
                        'app' => self::APP_ID,
                    ]);
                    return true;
                }
                return false;
            }
        } catch (\Exception $e) {
            $this->logger->error('Failed to ensure Lager folder', [
                'lagerId' => $lager->getId(),
                'path' => $path,
                'exception' => $e,
                'app' => self::APP_ID,
            ]);
            return false;
        }
    }

    /**
     * Ensure Lager folder exists by Lager ID.
     *
     * @param int $lagerId The Lager ID
     * @param bool $force Create the folder regardless of auto-create setting
     * @return array{exists: bool, path: string}
     * @throws DoesNotExistException
     */
    public function ensureLagerFolderById(int $lagerId, bool $force = false): array {
        $lager = $this->lagerMapper->findById($lagerId);
        $exists = $this->ensureLagerFolder($lager, $force);
        return [
            'exists' => $exists,
            'path' => $this->getLagerFolderPath($lager),
        ];
    }

    /**
     * Get files for a Lager's folder.
     *
     * @param int $lagerId The Lager ID
     * @return array{exists: bool, path: string, files: array}
     * @throws DoesNotExistException
     */
    public function getLagerFilesById(int $lagerId): array {
        $lager = $this->lagerMapper->findById($lagerId);
        $path = $this->getLagerFolderPath($lager);

        try {
            $files = $this->listFolderContents($path);
            return [
                'exists' => true,
                'path' => $path,
                'files' => $files,
            ];
        } catch (NotFoundException $e) {
            return [
                'exists' => false,
                'path' => $path,
                'files' => [],
            ];
        } catch (\Exception $e) {
            $this->logger->error('Failed to list Lager files', [
                'lagerId' => $lagerId,
                'path' => $path,
                'exception' => $e,
                'app' => self::APP_ID,
            ]);
            return [
                'exists' => false,
                'path' => $path,
                'files' => [],
            ];
        }
    }

    // ── Settings ────────────────────────────────────────────────────

    /**
     * Get the base path for member folders.
     */
    public function getBasePath(): string {
        return $this->config->getAppValue(self::APP_ID, 'file_base_path', self::DEFAULT_BASE_PATH);
    }

    /**
     * Get the subfolder naming pattern.
     */
    public function getSubfolderPattern(): string {
        return $this->config->getAppValue(self::APP_ID, 'file_subfolder_pattern', self::DEFAULT_SUBFOLDER_PATTERN);
    }

    /**
     * Check if auto-create is enabled.
     */
    public function isAutoCreateEnabled(): bool {
        return $this->config->getAppValue(self::APP_ID, 'file_auto_create', self::DEFAULT_AUTO_CREATE) === 'true';
    }

    /**
     * Get the base path for Lager folders.
     */
    public function getLagerBasePath(): string {
        return $this->config->getAppValue(self::APP_ID, 'file_lager_base_path', self::DEFAULT_LAGER_BASE_PATH);
    }

    /**
     * Get all file settings as an array.
     *
     * @return array All current settings
     */
    public function getSettings(): array {
        return [
            'basePath' => $this->getBasePath(),
            'subfolderPattern' => $this->getSubfolderPattern(),
            'autoCreate' => $this->isAutoCreateEnabled(),
            'lagerBasePath' => $this->getLagerBasePath(),
        ];
    }

    /**
     * Update file settings.
     *
     * @param array $settings Settings to update
     */
    public function updateSettings(array $settings): void {
        if (isset($settings['basePath'])) {
            $this->config->setAppValue(self::APP_ID, 'file_base_path', trim($settings['basePath']));
        }
        if (isset($settings['subfolderPattern'])) {
            $this->config->setAppValue(self::APP_ID, 'file_subfolder_pattern', trim($settings['subfolderPattern']));
        }
        if (array_key_exists('autoCreate', $settings)) {
            $this->config->setAppValue(self::APP_ID, 'file_auto_create', $settings['autoCreate'] ? 'true' : 'false');
        }
        if (isset($settings['lagerBasePath'])) {
            $this->config->setAppValue(self::APP_ID, 'file_lager_base_path', trim($settings['lagerBasePath']));
        }
    }

    // ── Helpers ──────────────────────────────────────────────────────

    /**
     * Apply the subfolder pattern to a member's data.
     *
     * Supported placeholders: {Vorname}, {Nachname}, {Id}
     *
     * @param string $pattern The pattern string
     * @param Member $member The member entity
     * @return string The computed subfolder name
     */
    private function applyPattern(string $pattern, Member $member): string {
        $replacements = [
            '{Vorname}' => $this->sanitizeFolderName($member->getVorname()),
            '{Nachname}' => $this->sanitizeFolderName($member->getNachname()),
            '{Id}' => (string)$member->getId(),
        ];

        return str_replace(
            array_keys($replacements),
            array_values($replacements),
            $pattern
        );
    }

    /**
     * Sanitize a string for use as a folder name.
     * Removes/replaces characters that are invalid in file paths.
     *
     * @param string $name The raw name
     * @return string The sanitized name
     */
    private function sanitizeFolderName(string $name): string {
        // Replace path separators and null bytes
        $name = str_replace(['/', '\\', "\0"], '_', $name);

        // Remove other problematic characters
        $name = preg_replace('/[<>:"|?*]/', '', $name);

        // Trim whitespace and dots
        $name = trim($name, " \t\n\r\0\x0B.");

        return $name ?: '_';
    }
}
