<?php
declare(strict_types=1);

namespace App;

final class CommunityAccess
{
    private array $tableCache = [];
    private array $columnCache = [];

    public function __construct(private \PDO $pdo, private array $communityConfig)
    {
    }

    public function getUserRoles(int $userId): array
    {
        if ($userId <= 0) {
            return [];
        }

        $roles = [];
        if ($this->hasTable('user_roles')) {
            $stmt = $this->pdo->prepare('SELECT role FROM user_roles WHERE user_id = :uid');
            $stmt->execute(['uid' => $userId]);
            $roles = array_map('strval', $stmt->fetchAll(\PDO::FETCH_COLUMN) ?: []);
        }

        if ($userId === 1 && !in_array('owner', $roles, true)) {
            $roles[] = 'owner';
        }

        if (in_array('owner', $roles, true)) {
            foreach (['site_admin', 'forum_admin'] as $derived) {
                if (!in_array($derived, $roles, true)) {
                    $roles[] = $derived;
                }
            }
        }

        if (in_array('site_admin', $roles, true) && !in_array('forum_admin', $roles, true)) {
            $roles[] = 'forum_admin';
        }

        return array_values(array_unique($roles));
    }

    public function hasRole(int $userId, string $role): bool
    {
        return in_array($role, $this->getUserRoles($userId), true);
    }

    public function canModerateForum(int $userId): bool
    {
        return $this->hasRole($userId, 'forum_admin');
    }

    public function canManageApplications(int $userId): bool
    {
        return $this->hasRole($userId, 'site_admin');
    }

    public function canManageRoles(int $userId): bool
    {
        return $this->hasRole($userId, 'owner');
    }

    public function getRestrictionState(int $userId): array
    {
        $state = [
            'thread_create_blocked' => false,
            'reply_blocked' => false,
            'reason' => null,
        ];

        if ($userId <= 0 || !$this->hasTable('community_user_restrictions')) {
            return $state;
        }

        $stmt = $this->pdo->prepare('
            SELECT restriction_type, reason
            FROM community_user_restrictions
            WHERE user_id = :uid
              AND active = 1
              AND (expires_at IS NULL OR expires_at > NOW())
        ');
        $stmt->execute(['uid' => $userId]);
        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [] as $row) {
            if (isset($state[$row['restriction_type']])) {
                $state[$row['restriction_type']] = true;
                if ($state['reason'] === null && !empty($row['reason'])) {
                    $state['reason'] = (string)$row['reason'];
                }
            }
        }

        return $state;
    }

    public function canCreateThread(int $userId): bool
    {
        return !$this->getRestrictionState($userId)['thread_create_blocked'];
    }

    public function canReply(int $userId): bool
    {
        return !$this->getRestrictionState($userId)['reply_blocked'];
    }

    public function canApplyForForumAdmin(int $userId, float $points): bool
    {
        if ($userId <= 0 || $points < 750.0 || !$this->hasTable('community_admin_applications')) {
            return false;
        }

        foreach (['forum_admin', 'site_admin', 'owner'] as $role) {
            if ($this->hasRole($userId, $role)) {
                return false;
            }
        }

        $latest = $this->getLatestApplication($userId);
        return !$latest || $latest['status'] !== 'open';
    }

    public function getLatestApplication(int $userId): ?array
    {
        if ($userId <= 0 || !$this->hasTable('community_admin_applications')) {
            return null;
        }

        $stmt = $this->pdo->prepare('
            SELECT caa.*, p.display_name AS decided_by_name
            FROM community_admin_applications caa
            LEFT JOIN user_profiles p ON p.user_id = caa.decided_by
            WHERE caa.user_id = :uid
            ORDER BY caa.created_at DESC
            LIMIT 1
        ');
        $stmt->execute(['uid' => $userId]);
        $row = $stmt->fetch(\PDO::FETCH_ASSOC);
        return $row ?: null;
    }

    public function submitApplication(int $userId, string $motivation): void
    {
        if (!$this->hasTable('community_admin_applications')) {
            throw new \RuntimeException('Admin-Bewerbungen stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        $motivation = trim($motivation);
        if ($motivation === '') {
            throw new \RuntimeException('Bitte begründe deine Bewerbung.');
        }

        $stmt = $this->pdo->prepare('INSERT INTO community_admin_applications (user_id, motivation, status) VALUES (:uid, :motivation, :status)');
        $stmt->execute([
            'uid' => $userId,
            'motivation' => $motivation,
            'status' => 'open',
        ]);
    }

    public function listApplications(string $status = 'open'): array
    {
        if (!$this->hasTable('community_admin_applications')) {
            return [];
        }

        $sql = '
            SELECT caa.*, up.display_name, u.email, dp.display_name AS decided_by_name
            FROM community_admin_applications caa
            JOIN users u ON u.id = caa.user_id
            LEFT JOIN user_profiles up ON up.user_id = caa.user_id
            LEFT JOIN user_profiles dp ON dp.user_id = caa.decided_by
        ';
        $params = [];
        if ($status !== '') {
            $sql .= ' WHERE caa.status = :status';
            $params['status'] = $status;
        }
        $sql .= ' ORDER BY caa.created_at DESC';

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
    }

    public function decideApplication(int $adminUserId, int $applicationId, string $decision, ?string $reason = null): void
    {
        if (!$this->canManageApplications($adminUserId)) {
            throw new \RuntimeException('Keine Berechtigung.');
        }
        if (!$this->hasTable('community_admin_applications')) {
            throw new \RuntimeException('Admin-Bewerbungen stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        $decision = $decision === 'approved' ? 'approved' : 'rejected';
        $stmt = $this->pdo->prepare('SELECT * FROM community_admin_applications WHERE id = :id LIMIT 1');
        $stmt->execute(['id' => $applicationId]);
        $application = $stmt->fetch(\PDO::FETCH_ASSOC);
        if (!$application) {
            throw new \RuntimeException('Bewerbung nicht gefunden.');
        }

        $update = $this->pdo->prepare('
            UPDATE community_admin_applications
            SET status = :status, decision_reason = :reason, decided_by = :decidedBy, decided_at = NOW()
            WHERE id = :id
        ');
        $update->execute([
            'status' => $decision,
            'reason' => trim((string)$reason) ?: null,
            'decidedBy' => $adminUserId,
            'id' => $applicationId,
        ]);

        if ($decision === 'approved') {
            $this->assignRole($adminUserId, (int)$application['user_id'], 'forum_admin');
        }
    }

    public function assignRole(int $actingUserId, int $targetUserId, string $role): void
    {
        if (!$this->canManageRoles($actingUserId) && !$this->canManageApplications($actingUserId)) {
            throw new \RuntimeException('Keine Berechtigung.');
        }
        if (!$this->hasTable('user_roles')) {
            throw new \RuntimeException('Rollen stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        if (!in_array($role, ['forum_admin', 'site_admin', 'owner'], true)) {
            throw new \RuntimeException('Ungültige Rolle.');
        }

        if ($role !== 'forum_admin' && !$this->canManageRoles($actingUserId)) {
            throw new \RuntimeException('Nur der Inhaber darf diese Rolle vergeben.');
        }

        $stmt = $this->pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role, assigned_by) VALUES (:uid, :role, :by)');
        $stmt->execute([
            'uid' => $targetUserId,
            'role' => $role,
            'by' => $actingUserId,
        ]);
    }

    public function revokeRole(int $actingUserId, int $targetUserId, string $role): void
    {
        if (!$this->canManageRoles($actingUserId)) {
            throw new \RuntimeException('Keine Berechtigung.');
        }
        if (!$this->hasTable('user_roles')) {
            throw new \RuntimeException('Rollen stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        $stmt = $this->pdo->prepare('DELETE FROM user_roles WHERE user_id = :uid AND role = :role');
        $stmt->execute([
            'uid' => $targetUserId,
            'role' => $role,
        ]);
    }

    public function listRoleAssignments(): array
    {
        if (!$this->hasTable('user_roles')) {
            return [];
        }

        $stmt = $this->pdo->query('
            SELECT ur.user_id, ur.role, ur.assigned_at, up.display_name, u.email
            FROM user_roles ur
            JOIN users u ON u.id = ur.user_id
            LEFT JOIN user_profiles up ON up.user_id = ur.user_id
            ORDER BY FIELD(ur.role, "owner", "site_admin", "forum_admin"), ur.assigned_at ASC
        ');
        return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
    }

    public function setRestriction(int $actingUserId, int $targetUserId, string $type, string $reason): void
    {
        if (!$this->canModerateForum($actingUserId)) {
            throw new \RuntimeException('Keine Berechtigung.');
        }
        if (!$this->hasTable('community_user_restrictions')) {
            throw new \RuntimeException('Community-Sperren stehen erst nach dem Datenbank-Update zur Verfügung.');
        }
        if (!in_array($type, ['thread_create_blocked', 'reply_blocked'], true)) {
            throw new \RuntimeException('Ungültige Sperre.');
        }

        $stmt = $this->pdo->prepare('
            INSERT INTO community_user_restrictions (user_id, restriction_type, reason, active, created_by)
            VALUES (:uid, :type, :reason, 1, :by)
        ');
        $stmt->execute([
            'uid' => $targetUserId,
            'type' => $type,
            'reason' => trim($reason) ?: null,
            'by' => $actingUserId,
        ]);
    }

    public function clearRestriction(int $actingUserId, int $targetUserId, string $type): void
    {
        if (!$this->canModerateForum($actingUserId)) {
            throw new \RuntimeException('Keine Berechtigung.');
        }
        if (!$this->hasTable('community_user_restrictions')) {
            throw new \RuntimeException('Community-Sperren stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        $stmt = $this->pdo->prepare('UPDATE community_user_restrictions SET active = 0 WHERE user_id = :uid AND restriction_type = :type');
        $stmt->execute([
            'uid' => $targetUserId,
            'type' => $type,
        ]);
    }

    public function submitReport(int $userId, string $targetType, int $targetId, string $reason): void
    {
        if (!$this->hasTable('forum_reports')) {
            throw new \RuntimeException('Meldungen stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        $stmt = $this->pdo->prepare('INSERT INTO forum_reports (reporter_user_id, target_type, target_id, reason, status) VALUES (:uid, :type, :targetId, :reason, :status)');
        $stmt->execute([
            'uid' => $userId,
            'type' => $targetType,
            'targetId' => $targetId,
            'reason' => trim($reason),
            'status' => 'open',
        ]);
    }

    public function listOpenReports(): array
    {
        if (!$this->hasTable('forum_reports')) {
            return [];
        }

        $stmt = $this->pdo->query('
            SELECT fr.*, up.display_name AS reporter_name
            FROM forum_reports fr
            LEFT JOIN user_profiles up ON up.user_id = fr.reporter_user_id
            WHERE fr.status = "open"
            ORDER BY fr.created_at DESC
        ');
        return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
    }

    public function resolveReport(int $actingUserId, int $reportId, string $note = ''): void
    {
        if (!$this->canModerateForum($actingUserId)) {
            throw new \RuntimeException('Keine Berechtigung.');
        }
        if (!$this->hasTable('forum_reports')) {
            throw new \RuntimeException('Meldungen stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        $stmt = $this->pdo->prepare('
            UPDATE forum_reports
            SET status = "resolved", moderator_user_id = :uid, moderator_note = :note, resolved_at = NOW()
            WHERE id = :id
        ');
        $stmt->execute([
            'uid' => $actingUserId,
            'note' => trim($note) ?: null,
            'id' => $reportId,
        ]);
    }

    public function votePost(int $userId, int $postId, int $value): void
    {
        if (!$this->hasTable('forum_post_feedback')) {
            throw new \RuntimeException('Bewertungen stehen erst nach dem Datenbank-Update zur Verfügung.');
        }

        $value = $value >= 1 ? 1 : -1;
        $stmt = $this->pdo->prepare('
            INSERT INTO forum_post_feedback (post_id, user_id, value)
            VALUES (:postId, :uid, :value)
            ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = CURRENT_TIMESTAMP
        ');
        $stmt->execute([
            'postId' => $postId,
            'uid' => $userId,
            'value' => $value,
        ]);
    }

    public function getPostFeedbackSummary(array $postIds): array
    {
        if (!$postIds || !$this->hasTable('forum_post_feedback')) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($postIds), '?'));
        $stmt = $this->pdo->prepare("
            SELECT post_id,
                   SUM(CASE WHEN value = 1 THEN 1 ELSE 0 END) AS helpful_count,
                   SUM(CASE WHEN value = -1 THEN 1 ELSE 0 END) AS unhelpful_count
            FROM forum_post_feedback
            WHERE post_id IN ($placeholders)
            GROUP BY post_id
        ");
        $stmt->execute(array_values($postIds));
        $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
        $out = [];
        foreach ($rows as $row) {
            $out[(int)$row['post_id']] = [
                'helpful' => (int)$row['helpful_count'],
                'unhelpful' => (int)$row['unhelpful_count'],
            ];
        }
        return $out;
    }

    public function getUserPostVotes(int $userId, array $postIds): array
    {
        if ($userId <= 0 || !$postIds || !$this->hasTable('forum_post_feedback')) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($postIds), '?'));
        $params = array_merge([$userId], array_values($postIds));
        $stmt = $this->pdo->prepare("
            SELECT post_id, value
            FROM forum_post_feedback
            WHERE user_id = ?
              AND post_id IN ($placeholders)
        ");
        $stmt->execute($params);
        $out = [];
        foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [] as $row) {
            $out[(int)$row['post_id']] = (int)$row['value'];
        }
        return $out;
    }

    public function canHighlightHelpful(float $points): bool
    {
        return $points >= 500.0;
    }

    public function supportsApplications(): bool
    {
        return $this->hasTable('community_admin_applications');
    }

    public function supportsReports(): bool
    {
        return $this->hasTable('forum_reports');
    }

    public function supportsFeedback(): bool
    {
        return $this->hasTable('forum_post_feedback');
    }

    public function supportsRestrictions(): bool
    {
        return $this->hasTable('community_user_restrictions');
    }

    public function hasModerationTables(): bool
    {
        return $this->hasTable('user_roles');
    }

    private function hasTable(string $table): bool
    {
        if (array_key_exists($table, $this->tableCache)) {
            return $this->tableCache[$table];
        }

        try {
            $stmt = $this->pdo->prepare('SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :table LIMIT 1');
            $stmt->execute(['table' => $table]);
            return $this->tableCache[$table] = (bool)$stmt->fetchColumn();
        } catch (\Throwable) {
            return $this->tableCache[$table] = false;
        }
    }
}
