<?php
declare(strict_types=1);

namespace App\Models;

use PDO;

class CommunityPost extends BaseModel
{
    protected string $table = 'community_posts';

    public function createPost(int $userId, string $type, array $data, ?string $imagePath, string $status, int $riskScore, array $pollOptions = []): array
    {
        $type = in_array($type, ['text', 'image', 'poll'], true) ? $type : 'text';
        $status = $this->normalizeStatus($status);
        $riskScore = min(100, max(0, $riskScore));

        $pdo = $this->db();
        $pdo->beginTransaction();

        try {
            $statement = $pdo->prepare(
                'INSERT INTO community_posts
                    (user_id, post_type, content, image_path, poll_question, status, risk_score, created_at, updated_at)
                 VALUES
                    (:user_id, :post_type, :content, :image_path, :poll_question, :status, :risk_score, NOW(), NOW())'
            );
            $statement->execute([
                'user_id' => $userId,
                'post_type' => $type,
                'content' => $this->nullableText((string) ($data['content'] ?? '')),
                'image_path' => $imagePath,
                'poll_question' => $this->nullableText((string) ($data['poll_question'] ?? '')),
                'status' => $status,
                'risk_score' => $riskScore,
            ]);

            $postId = (int) $pdo->lastInsertId();
            if ($type === 'poll') {
                $insertOption = $pdo->prepare(
                    'INSERT INTO community_poll_options (post_id, option_text, vote_count, sort_order, created_at)
                     VALUES (:post_id, :option_text, 0, :sort_order, NOW())'
                );
                foreach (array_values($pollOptions) as $index => $option) {
                    $option = trim((string) $option);
                    if ($option === '') {
                        continue;
                    }
                    $insertOption->execute([
                        'post_id' => $postId,
                        'option_text' => function_exists('mb_substr') ? mb_substr($option, 0, 160) : substr($option, 0, 160),
                        'sort_order' => $index + 1,
                    ]);
                }
            }

            $pdo->commit();

            return $this->findDetailed($postId, $userId) ?? [];
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }

            throw $exception;
        }
    }

    public function listFeed(?int $viewerId, int $limit = 8): array
    {
        $limit = min(max($limit, 1), 40);
        $viewerId = (int) ($viewerId ?? 0);

        $sql = $this->baseSelect() .
            ' WHERE cp.status = "published" AND cp.deleted_at IS NULL
              ORDER BY
                CASE WHEN :viewer_order > 0 AND (
                    cp.user_id = :viewer_self
                    OR EXISTS (SELECT 1 FROM user_follows uf WHERE uf.follower_id = :viewer_follow AND uf.followed_id = cp.user_id)
                    OR EXISTS (
                        SELECT 1 FROM friendships f
                        WHERE f.status = "accepted"
                          AND (
                            (f.requester_id = :viewer_friend_a AND f.receiver_id = cp.user_id)
                            OR (f.receiver_id = :viewer_friend_b AND f.requester_id = cp.user_id)
                          )
                    )
                ) THEN 0 ELSE 1 END,
                cp.created_at DESC
              LIMIT :limit';

        $statement = $this->db()->prepare($sql);
        foreach (['viewer_order', 'viewer_self', 'viewer_follow', 'viewer_friend_a', 'viewer_friend_b', 'viewer_reaction', 'viewer_vote'] as $key) {
            $statement->bindValue($key, $viewerId, PDO::PARAM_INT);
        }
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $row): array => $this->withPollOptions($this->normalize($row), $viewerId), $statement->fetchAll());
    }

    public function listForUser(int $userId, ?int $viewerId, int $limit = 24): array
    {
        $limit = min(max($limit, 1), 80);
        $viewerId = (int) ($viewerId ?? 0);

        $statement = $this->db()->prepare(
            $this->baseSelect() .
            ' WHERE cp.user_id = :user_id
                AND cp.status = "published"
                AND cp.deleted_at IS NULL
              ORDER BY cp.created_at DESC
              LIMIT :limit'
        );
        $statement->bindValue('user_id', $userId, PDO::PARAM_INT);
        $statement->bindValue('viewer_reaction', $viewerId, PDO::PARAM_INT);
        $statement->bindValue('viewer_vote', $viewerId, PDO::PARAM_INT);
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $row): array => $this->withPollOptions($this->normalize($row), $viewerId), $statement->fetchAll());
    }

    public function listForOwnerStudio(int $userId, int $limit = 8): array
    {
        $limit = min(max($limit, 1), 30);
        $statement = $this->db()->prepare(
            $this->baseSelect() .
            ' WHERE cp.user_id = :user_id
                AND cp.status <> "deleted"
                AND cp.deleted_at IS NULL
              ORDER BY cp.created_at DESC
              LIMIT :limit'
        );
        $statement->bindValue('user_id', $userId, PDO::PARAM_INT);
        $statement->bindValue('viewer_reaction', $userId, PDO::PARAM_INT);
        $statement->bindValue('viewer_vote', $userId, PDO::PARAM_INT);
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $row): array => $this->withPollOptions($this->normalize($row), $userId), $statement->fetchAll());
    }

    public function findDetailed(int $postId, ?int $viewerId = null): ?array
    {
        $viewerId = (int) ($viewerId ?? 0);
        $statement = $this->db()->prepare(
            $this->baseSelect() .
            ' WHERE cp.id = :id AND cp.deleted_at IS NULL
              LIMIT 1'
        );
        $statement->bindValue('id', $postId, PDO::PARAM_INT);
        $statement->bindValue('viewer_reaction', $viewerId, PDO::PARAM_INT);
        $statement->bindValue('viewer_vote', $viewerId, PDO::PARAM_INT);
        $statement->execute();
        $row = $statement->fetch();

        return $row ? $this->withPollOptions($this->normalize($row), $viewerId) : null;
    }

    public function toggleReaction(int $postId, int $userId, string $reaction): array
    {
        if (!in_array($reaction, ['like', 'dislike'], true)) {
            throw new \InvalidArgumentException('Reaksiyon gecersiz.');
        }

        $pdo = $this->db();
        $pdo->beginTransaction();

        try {
            $existing = $pdo->prepare('SELECT id, reaction FROM community_reactions WHERE post_id = :post_id AND user_id = :user_id LIMIT 1');
            $existing->execute(['post_id' => $postId, 'user_id' => $userId]);
            $row = $existing->fetch();

            if ($row && (string) $row['reaction'] === $reaction) {
                $pdo->prepare('DELETE FROM community_reactions WHERE id = :id')->execute(['id' => (int) $row['id']]);
                $viewerReaction = null;
            } elseif ($row) {
                $pdo->prepare('UPDATE community_reactions SET reaction = :reaction, created_at = NOW() WHERE id = :id')
                    ->execute(['reaction' => $reaction, 'id' => (int) $row['id']]);
                $viewerReaction = $reaction;
            } else {
                $pdo->prepare(
                    'INSERT INTO community_reactions (post_id, user_id, reaction, created_at)
                     VALUES (:post_id, :user_id, :reaction, NOW())'
                )->execute(['post_id' => $postId, 'user_id' => $userId, 'reaction' => $reaction]);
                $viewerReaction = $reaction;
            }

            $pdo->commit();
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $exception;
        }

        return $this->counts($postId) + ['viewer_reaction' => $viewerReaction];
    }

    public function votePoll(int $postId, int $optionId, int $userId): array
    {
        $pdo = $this->db();
        $pdo->beginTransaction();

        try {
            $option = $pdo->prepare(
                'SELECT cpo.id
                 FROM community_poll_options cpo
                 INNER JOIN community_posts cp ON cp.id = cpo.post_id
                 WHERE cpo.id = :option_id
                   AND cpo.post_id = :post_id
                   AND cp.post_type = "poll"
                   AND cp.status = "published"
                   AND cp.deleted_at IS NULL
                 LIMIT 1'
            );
            $option->execute(['option_id' => $optionId, 'post_id' => $postId]);
            if (!$option->fetchColumn()) {
                throw new \InvalidArgumentException('Anket secenegi bulunamadi.');
            }

            $existing = $pdo->prepare('SELECT id, option_id FROM community_poll_votes WHERE post_id = :post_id AND user_id = :user_id LIMIT 1');
            $existing->execute(['post_id' => $postId, 'user_id' => $userId]);
            $vote = $existing->fetch();

            if ($vote && (int) $vote['option_id'] !== $optionId) {
                $pdo->prepare('UPDATE community_poll_options SET vote_count = GREATEST(vote_count - 1, 0) WHERE id = :id')
                    ->execute(['id' => (int) $vote['option_id']]);
                $pdo->prepare('UPDATE community_poll_votes SET option_id = :option_id, created_at = NOW() WHERE id = :id')
                    ->execute(['option_id' => $optionId, 'id' => (int) $vote['id']]);
                $pdo->prepare('UPDATE community_poll_options SET vote_count = vote_count + 1 WHERE id = :id')
                    ->execute(['id' => $optionId]);
            } elseif (!$vote) {
                $pdo->prepare(
                    'INSERT INTO community_poll_votes (post_id, option_id, user_id, created_at)
                     VALUES (:post_id, :option_id, :user_id, NOW())'
                )->execute(['post_id' => $postId, 'option_id' => $optionId, 'user_id' => $userId]);
                $pdo->prepare('UPDATE community_poll_options SET vote_count = vote_count + 1 WHERE id = :id')
                    ->execute(['id' => $optionId]);
            }

            $pdo->commit();

            return $this->withPollOptions($this->findDetailed($postId, $userId) ?? [], $userId);
        } catch (\Throwable $exception) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $exception;
        }
    }

    public function softDelete(int $postId, int $userId, bool $isAdmin = false): bool
    {
        $sql = 'UPDATE community_posts SET status = "deleted", deleted_at = NOW(), updated_at = NOW() WHERE id = :id';
        $params = ['id' => $postId];
        if (!$isAdmin) {
            $sql .= ' AND user_id = :user_id';
            $params['user_id'] = $userId;
        }

        $statement = $this->db()->prepare($sql);
        $statement->execute($params);

        return $statement->rowCount() > 0;
    }

    public function setStatus(int $postId, string $status): bool
    {
        $status = $this->normalizeStatus($status);
        $statement = $this->db()->prepare(
            'UPDATE community_posts
             SET status = :status,
                 deleted_at = IF(:deleted_status = "deleted", COALESCE(deleted_at, NOW()), deleted_at),
                 updated_at = NOW()
             WHERE id = :id'
        );
        $statement->execute([
            'status' => $status,
            'deleted_status' => $status,
            'id' => $postId,
        ]);

        return $statement->rowCount() > 0;
    }

    public function listForAdmin(array $filters = [], int $limit = 160): array
    {
        $limit = min(max($limit, 1), 300);
        $where = [];
        $params = [];

        if (!empty($filters['status'])) {
            $where[] = 'cp.status = :status';
            $params['status'] = (string) $filters['status'];
        } else {
            $where[] = 'cp.status <> "deleted"';
        }

        if (!empty($filters['post_type'])) {
            $where[] = 'cp.post_type = :post_type';
            $params['post_type'] = (string) $filters['post_type'];
        }

        if (!empty($filters['q'])) {
            $where[] = '(cp.content LIKE :q_content OR cp.poll_question LIKE :q_poll OR u.username LIKE :q_user OR up.display_name LIKE :q_display)';
            $like = '%' . trim((string) $filters['q']) . '%';
            $params['q_content'] = $like;
            $params['q_poll'] = $like;
            $params['q_user'] = $like;
            $params['q_display'] = $like;
        }

        $sql = $this->baseSelect() . ' WHERE ' . implode(' AND ', $where) . ' ORDER BY cp.created_at DESC LIMIT :limit';
        $statement = $this->db()->prepare($sql);
        foreach ($params as $key => $value) {
            $statement->bindValue($key, $value);
        }
        $statement->bindValue('viewer_reaction', 0, PDO::PARAM_INT);
        $statement->bindValue('viewer_vote', 0, PDO::PARAM_INT);
        $statement->bindValue('limit', $limit, PDO::PARAM_INT);
        $statement->execute();

        return array_map(fn (array $row): array => $this->withPollOptions($this->normalize($row), null), $statement->fetchAll());
    }

    public function summary(): array
    {
        return [
            'total' => (int) $this->db()->query('SELECT COUNT(*) FROM community_posts WHERE status <> "deleted"')->fetchColumn(),
            'published' => (int) $this->db()->query('SELECT COUNT(*) FROM community_posts WHERE status = "published"')->fetchColumn(),
            'pending' => (int) $this->db()->query('SELECT COUNT(*) FROM community_posts WHERE status = "pending_review"')->fetchColumn(),
            'blocked' => (int) $this->db()->query('SELECT COUNT(*) FROM community_posts WHERE status = "blocked"')->fetchColumn(),
        ];
    }

    private function baseSelect(): string
    {
        return 'SELECT cp.*,
                       u.username,
                       u.nickname AS user_nickname,
                       COALESCE(NULLIF(up.display_name, ""), NULLIF(u.nickname, ""), u.username, "User") AS display_name,
                       COALESCE(NULLIF(up.nickname, ""), u.username) AS nickname,
                       COALESCE(NULLIF(up.avatar, ""), "") AS avatar,
                       (SELECT COUNT(*) FROM community_reactions cr WHERE cr.post_id = cp.id AND cr.reaction = "like") AS like_count,
                       (SELECT COUNT(*) FROM community_reactions cr WHERE cr.post_id = cp.id AND cr.reaction = "dislike") AS dislike_count,
                       (SELECT COUNT(*) FROM community_comments cc WHERE cc.post_id = cp.id AND cc.status IN ("published", "visible")) AS comment_count,
                       (SELECT cr.reaction FROM community_reactions cr WHERE cr.post_id = cp.id AND cr.user_id = :viewer_reaction LIMIT 1) AS viewer_reaction,
                       (SELECT cpv.option_id FROM community_poll_votes cpv WHERE cpv.post_id = cp.id AND cpv.user_id = :viewer_vote LIMIT 1) AS viewer_poll_option_id
                FROM community_posts cp
                LEFT JOIN users u ON u.id = cp.user_id
                LEFT JOIN user_profiles up ON up.user_id = u.id';
    }

    private function withPollOptions(array $post, ?int $viewerId): array
    {
        if (($post['post_type'] ?? '') !== 'poll' || (int) ($post['id'] ?? 0) <= 0) {
            $post['poll_options'] = [];
            return $post;
        }

        $statement = $this->db()->prepare(
            'SELECT id, post_id, option_text, vote_count, sort_order, created_at
             FROM community_poll_options
             WHERE post_id = :post_id
             ORDER BY sort_order ASC, id ASC'
        );
        $statement->execute(['post_id' => (int) $post['id']]);
        $options = $statement->fetchAll();
        $total = array_sum(array_map(static fn (array $option): int => (int) ($option['vote_count'] ?? 0), $options));
        $viewerOption = (int) ($post['viewer_poll_option_id'] ?? 0);

        $post['poll_total_votes'] = $total;
        $post['poll_options'] = array_map(static function (array $option) use ($total, $viewerOption): array {
            $votes = (int) ($option['vote_count'] ?? 0);
            return [
                'id' => (int) $option['id'],
                'post_id' => (int) $option['post_id'],
                'option_text' => (string) $option['option_text'],
                'vote_count' => $votes,
                'percent' => $total > 0 ? (int) round(($votes / $total) * 100) : 0,
                'viewer_voted' => $viewerOption === (int) $option['id'],
            ];
        }, $options);

        return $post;
    }

    private function counts(int $postId): array
    {
        $statement = $this->db()->prepare(
            'SELECT
                (SELECT COUNT(*) FROM community_reactions WHERE post_id = :like_post AND reaction = "like") AS like_count,
                (SELECT COUNT(*) FROM community_reactions WHERE post_id = :dislike_post AND reaction = "dislike") AS dislike_count,
                (SELECT COUNT(*) FROM community_comments WHERE post_id = :comment_post AND status IN ("published", "visible")) AS comment_count'
        );
        $statement->execute([
            'like_post' => $postId,
            'dislike_post' => $postId,
            'comment_post' => $postId,
        ]);
        $row = $statement->fetch() ?: [];

        return [
            'like_count' => (int) ($row['like_count'] ?? 0),
            'dislike_count' => (int) ($row['dislike_count'] ?? 0),
            'comment_count' => (int) ($row['comment_count'] ?? 0),
        ];
    }

    private function normalize(array $row): array
    {
        return [
            'id' => (int) ($row['id'] ?? 0),
            'user_id' => (int) ($row['user_id'] ?? 0),
            'post_type' => (string) ($row['post_type'] ?? 'text'),
            'content' => (string) ($row['content'] ?? ''),
            'image_path' => (string) ($row['image_path'] ?? ''),
            'poll_question' => (string) ($row['poll_question'] ?? ''),
            'status' => (string) ($row['status'] ?? 'published'),
            'risk_score' => (int) ($row['risk_score'] ?? 0),
            'created_at' => (string) ($row['created_at'] ?? ''),
            'updated_at' => (string) ($row['updated_at'] ?? ''),
            'username' => (string) ($row['username'] ?? 'user'),
            'display_name' => (string) ($row['display_name'] ?? 'User'),
            'nickname' => (string) ($row['nickname'] ?? $row['username'] ?? 'user'),
            'avatar' => (string) ($row['avatar'] ?? ''),
            'like_count' => (int) ($row['like_count'] ?? 0),
            'dislike_count' => (int) ($row['dislike_count'] ?? 0),
            'comment_count' => (int) ($row['comment_count'] ?? 0),
            'viewer_reaction' => $row['viewer_reaction'] ?? null,
            'viewer_poll_option_id' => isset($row['viewer_poll_option_id']) ? (int) $row['viewer_poll_option_id'] : 0,
            'poll_options' => [],
        ];
    }

    private function nullableText(string $value): ?string
    {
        $value = trim($value);

        return $value === '' ? null : $value;
    }

    private function normalizeStatus(string $status): string
    {
        return in_array($status, ['published', 'pending_review', 'blocked', 'deleted'], true) ? $status : 'published';
    }
}
