<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Service;

use OCA\Mitgliederverwaltung\Db\MemberMapper;
use OCA\Mitgliederverwaltung\Db\Permission;
use OCA\Mitgliederverwaltung\Db\PermissionMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\DB\Exception;
use OCP\IGroupManager;

/**
 * Permission checking logic for all access levels.
 *
 * Access levels (lowest to highest):
 * - none: no access
 * - read: view members, search, generate reports (no banking)
 * - stufe: read/write for members in assigned Stufe(n) only
 * - full: full CRUD on all data
 * - admin: full + settings, fee rules, user permissions, import
 *
 * Part of Issue #36.
 */
class PermissionService {

    private PermissionMapper $permissionMapper;
    private MemberMapper $memberMapper;
    private IGroupManager $groupManager;

    public function __construct(
        PermissionMapper $permissionMapper,
        MemberMapper $memberMapper,
        IGroupManager $groupManager
    ) {
        $this->permissionMapper = $permissionMapper;
        $this->memberMapper = $memberMapper;
        $this->groupManager = $groupManager;
    }

    /**
     * Get permission record for a user. Returns null if no record exists (= no access).
     *
     * @throws Exception
     */
    public function getUserPermission(string $userId): ?Permission {
        try {
            return $this->permissionMapper->findByUserId($userId);
        } catch (DoesNotExistException $e) {
            return null;
        }
    }

    /**
     * Check if user has any access to the app (level != 'none').
     *
     * @throws Exception
     */
    public function canAccess(string $userId): bool {
        if ($this->groupManager->isInGroup($userId, 'admin')) {
            return true;
        }
        $perm = $this->getUserPermission($userId);
        return $perm !== null && $perm->getLevel() !== 'none';
    }

    /**
     * Check if user has at least read access.
     *
     * @throws Exception
     */
    public function canRead(string $userId): bool {
        if ($this->groupManager->isInGroup($userId, 'admin')) {
            return true;
        }
        $perm = $this->getUserPermission($userId);
        if ($perm === null) {
            return false;
        }
        return in_array($perm->getLevel(), ['read', 'stufe', 'full', 'admin'], true);
    }

    /**
     * Check if user can write data for a specific member.
     * - 'full' and 'admin' can write any member
     * - 'stufe' can only write members in their allowed Stufen
     *
     * @throws Exception
     */
    public function canWrite(string $userId, ?int $memberId = null): bool {
        if ($this->groupManager->isInGroup($userId, 'admin')) {
            return true;
        }
        $perm = $this->getUserPermission($userId);
        if ($perm === null) {
            return false;
        }

        $level = $perm->getLevel();

        if (in_array($level, ['full', 'admin'], true)) {
            return true;
        }

        if ($level === 'stufe' && $memberId !== null) {
            try {
                $member = $this->memberMapper->findById($memberId);
                $stufeId = $member->getStufeId();
                if ($stufeId !== null) {
                    return in_array($stufeId, $perm->getAllowedStufen(), true);
                }
            } catch (DoesNotExistException $e) {
                return false;
            }
        }

        return false;
    }

    /**
     * Check if user can write to members in a specific Stufe.
     *
     * @throws Exception
     */
    public function canWriteStufe(string $userId, int $stufeId): bool {
        $perm = $this->getUserPermission($userId);
        if ($perm === null) {
            return false;
        }

        $level = $perm->getLevel();

        if (in_array($level, ['full', 'admin'], true)) {
            return true;
        }

        if ($level === 'stufe') {
            return in_array($stufeId, $perm->getAllowedStufen(), true);
        }

        return false;
    }

    /**
     * Check if user is admin.
     *
     * @throws Exception
     */
    public function isAdmin(string $userId): bool {
        $perm = $this->getUserPermission($userId);
        if ($perm !== null && $perm->getLevel() === 'admin') {
            return true;
        }
        // Fallback: Nextcloud system admins are always app admins
        return $this->groupManager->isInGroup($userId, 'admin');
    }

    /**
     * Check if user can see banking details.
     *
     * @throws Exception
     */
    public function canSeeBanking(string $userId): bool {
        if ($this->groupManager->isInGroup($userId, 'admin')) {
            return true;
        }
        $perm = $this->getUserPermission($userId);
        return $perm !== null && $perm->getCanSeeBanking();
    }

    /**
     * Get the list of Stufe IDs visible to a user with Stufenzugriff.
     * Returns null for full/admin (= all Stufen visible).
     *
     * @return int[]|null null means all Stufen visible
     * @throws Exception
     */
    public function getVisibleStufen(string $userId): ?array {
        $perm = $this->getUserPermission($userId);
        if ($perm === null) {
            return [];
        }

        $level = $perm->getLevel();

        if (in_array($level, ['full', 'admin', 'read'], true)) {
            return null; // all visible
        }

        if ($level === 'stufe') {
            return $perm->getAllowedStufen();
        }

        return [];
    }

    // ── Admin methods for managing permissions ───────────────────────

    /**
     * Get all permission records.
     *
     * @return Permission[]
     * @throws Exception
     */
    public function getAllPermissions(): array {
        return $this->permissionMapper->findAll();
    }

    /**
     * Set permission for a user (create or update).
     *
     * @throws Exception
     */
    public function setPermission(
        string $ncUserId,
        string $level,
        ?array $allowedStufen = null,
        bool $canSeeBanking = false
    ): Permission {
        $validLevels = ['none', 'read', 'stufe', 'full', 'admin'];
        if (!in_array($level, $validLevels, true)) {
            throw new ValidationException(
                'Ungueltiges Berechtigungslevel: ' . $level . '. Erlaubt: ' . implode(', ', $validLevels)
            );
        }

        try {
            $perm = $this->permissionMapper->findByUserId($ncUserId);
            $perm->setLevel($level);
            $perm->setAllowedStufenJson($allowedStufen !== null ? json_encode($allowedStufen) : null);
            $perm->setCanSeeBanking($canSeeBanking);
            /** @var Permission $perm */
            return $this->permissionMapper->update($perm);
        } catch (DoesNotExistException $e) {
            $perm = new Permission();
            $perm->setNcUserId($ncUserId);
            $perm->setLevel($level);
            $perm->setAllowedStufenJson($allowedStufen !== null ? json_encode($allowedStufen) : null);
            $perm->setCanSeeBanking($canSeeBanking);
            /** @var Permission $perm */
            return $this->permissionMapper->insert($perm);
        }
    }

    /**
     * Remove permission for a user.
     *
     * @throws Exception
     */
    public function removePermission(string $ncUserId): void {
        try {
            $perm = $this->permissionMapper->findByUserId($ncUserId);
            $this->permissionMapper->delete($perm);
        } catch (DoesNotExistException $e) {
            // Already no permission
        }
    }
}
