<?php

namespace App\Rating\Controller;

use App\Appointment\Repository\AppointmentRepository;
use App\Auth\Entity\User;
use App\Doctor\Repository\DoctorRepository;
use App\Rating\Entity\Comment;
use App\Rating\Entity\Like;
use App\Rating\Entity\Rate;
use App\Rating\Repository\CommentRepository;
use App\Rating\Repository\LikeRepository;
use App\Rating\Repository\RateRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use OpenApi\Attributes as OA;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[OA\Tag(name: 'Ratings & Comments')]
class RatingController extends BaseController
{
    public function __construct(
        private readonly RateRepository    $rateRepo,
        private readonly CommentRepository $commentRepo,
        private readonly LikeRepository    $likeRepo,
        private readonly DoctorRepository  $doctorRepo,
        private readonly AppointmentRepository $appointmentRepo,
    ) {}

    // ── Ratings ───────────────────────────────────────────────────────────────

    #[OA\Post(
        path: '/api/v1/rate',
        summary: 'Submit or update a rating for a doctor',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['doctor_uuid', 'score'],
                properties: [
                    new OA\Property(property: 'doctor_uuid', type: 'string', description: 'UUID of the doctor'),
                    new OA\Property(property: 'score', type: 'integer', minimum: 1, maximum: 5, description: 'Rating score (1–5)'),
                ]
            )
        ),
        responses: [
            new OA\Response(
                response: 201,
                description: 'Rate object created',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(property: 'data', type: 'object', description: 'Rate object'),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 404, description: 'Doctor not found'),
            new OA\Response(response: 422, description: 'Validation error — score out of range'),
        ]
    )]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/rate', methods: ['POST'])]
    public function rate(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data       = json_decode($request->getContent(), true) ?? [];
        $doctorUuid = trim($data['doctor_uuid'] ?? '');

        $dimensions = [];
        foreach (array_keys(Rate::DIMENSIONS) as $name) {
            $value = (int) ($data[$name] ?? -1);
            if ($value < 0 || $value > 100) {
                return $this->error(ErrorCodes::ERR_VALIDATION_001, 'هر امتیاز باید بین ۰ تا ۱۰۰ باشد', 422);
            }
            $dimensions[$name] = $value;
        }

        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
        }

        if (!$this->appointmentRepo->hasRecentConfirmed($user, $doctor)) {
            return $this->error(ErrorCodes::ERR_RATING_NOT_ELIGIBLE, ErrorCodes::message(ErrorCodes::ERR_RATING_NOT_ELIGIBLE), 403);
        }

        $existing = $this->rateRepo->findByUserAndDoctor($user, $doctor);
        if ($existing !== null) {
            $existing->setDimensions($dimensions);
            $this->rateRepo->save($existing);
            return $this->success(['data' => $this->rateRepo->getAggregate($doctor)]);
        }

        $rate = new Rate($user, $doctor, $dimensions);
        $this->rateRepo->save($rate);

        return $this->success(['data' => $this->rateRepo->getAggregate($doctor)], 201);
    }

    #[OA\Get(
        path: '/api/v1/rate/{doctorUuid}',
        summary: 'Get average rating for a doctor',
        parameters: [
            new OA\Parameter(name: 'doctorUuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Average rating',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(
                            property: 'data',
                            properties: [
                                new OA\Property(property: 'average', type: 'number', format: 'float'),
                            ],
                            type: 'object'
                        ),
                    ]
                )
            ),
            new OA\Response(response: 404, description: 'Doctor not found'),
        ]
    )]
    #[Route('/api/v1/rate/{doctorUuid}', methods: ['GET'])]
    public function getAverage(string $doctorUuid): JsonResponse
    {
        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
        }

        return $this->success(['data' => $this->rateRepo->getAggregate($doctor)]);
    }

    #[OA\Get(
        path: '/api/v1/rate/{doctorUuid}/eligibility',
        summary: 'Whether the current user may rate/comment on this doctor',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'doctorUuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Eligibility status',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(
                            property: 'data',
                            properties: [
                                new OA\Property(property: 'eligible', type: 'boolean'),
                            ],
                            type: 'object'
                        ),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 404, description: 'Doctor not found'),
        ]
    )]
    #[Route('/api/v1/rate/{doctorUuid}/eligibility', methods: ['GET'])]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    public function eligibility(string $doctorUuid, #[CurrentUser] User $user): JsonResponse
    {
        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
        }

        return $this->success(['eligible' => $this->appointmentRepo->hasRecentConfirmed($user, $doctor)]);
    }

    // ── Comments ──────────────────────────────────────────────────────────────

    #[OA\Post(
        path: '/api/v1/comment',
        summary: 'Submit a comment for a doctor',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['doctor_uuid', 'body'],
                properties: [
                    new OA\Property(property: 'doctor_uuid', type: 'string', description: 'UUID of the doctor'),
                    new OA\Property(property: 'body', type: 'string', description: 'Comment text'),
                ]
            )
        ),
        responses: [
            new OA\Response(
                response: 201,
                description: 'Comment created',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(property: 'data', type: 'object', description: 'Comment object'),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 404, description: 'Doctor not found'),
            new OA\Response(response: 422, description: 'Validation error'),
        ]
    )]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/comment', methods: ['POST'])]
    public function createComment(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $data       = json_decode($request->getContent(), true) ?? [];
        $doctorUuid = trim($data['doctor_uuid'] ?? '');
        $body       = trim($data['comment'] ?? '');
        $parentUuid = trim($data['parent'] ?? '');

        if (empty($body)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'متن نظر الزامی است', 422);
        }

        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
        }

        if (!$this->appointmentRepo->hasRecentConfirmed($user, $doctor)) {
            return $this->error(ErrorCodes::ERR_RATING_NOT_ELIGIBLE, ErrorCodes::message(ErrorCodes::ERR_RATING_NOT_ELIGIBLE), 403);
        }

        $parent = null;
        if ($parentUuid !== '') {
            $parent = $this->commentRepo->findByUuid($parentUuid);
            if ($parent === null) {
                return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نظر والد یافت نشد', 404);
            }
        }

        $comment = new Comment($user, $doctor, $body, $parent);
        $this->commentRepo->save($comment);

        return $this->success(['data' => $comment->toArray($user)], 201);
    }

    #[OA\Get(
        path: '/api/v1/comments/{doctorUuid}',
        summary: 'List approved comments for a doctor',
        parameters: [
            new OA\Parameter(name: 'doctorUuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Array of comment objects',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(property: 'data', type: 'array', items: new OA\Items(type: 'object')),
                    ]
                )
            ),
            new OA\Response(response: 404, description: 'Doctor not found'),
        ]
    )]
    #[Route('/api/v1/comments/{doctorUuid}', methods: ['GET'])]
    public function listComments(string $doctorUuid, Request $request): JsonResponse
    {
        $doctor = $this->doctorRepo->findByUuid($doctorUuid);
        if ($doctor === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'دکتر یافت نشد', 404);
        }

        $page  = max(1, (int) $request->query->get('page', 1));
        $limit = min(100, max(1, (int) $request->query->get('limit', 50)));

        $comments = array_map(
            fn(Comment $c) => $c->toArray(),
            $this->commentRepo->findApprovedRootsByDoctor($doctor, $limit, ($page - 1) * $limit)
        );
        $total = $this->commentRepo->countApprovedRootsByDoctor($doctor);

        return $this->success([
            'data' => $comments,
            'meta' => [
                'totalRecords' => $total,
                'totalPages'   => (int) ceil($total / $limit),
                'currentPage'  => $page,
            ],
        ]);
    }

    #[OA\Delete(
        path: '/api/v1/comment/{uuid}',
        summary: 'Delete a comment (owner or ROLE_ADMIN)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Comment deleted',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(property: 'data', type: 'object', properties: [
                            new OA\Property(property: 'message', type: 'string'),
                        ]),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 403, description: 'Forbidden'),
            new OA\Response(response: 404, description: 'Comment not found'),
        ]
    )]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/comment/{uuid}', methods: ['DELETE'])]
    public function deleteComment(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        $comment = $this->commentRepo->findByUuid($uuid);
        if ($comment === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نظر یافت نشد', 404);
        }

        if ($comment->getUser()->getId() !== $user->getId() && !$user->hasRole('ROLE_ADMIN')) {
            return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
        }

        $this->commentRepo->remove($comment);
        return $this->success(['message' => 'نظر با موفقیت حذف شد']);
    }

    // ── Admin: comment moderation ─────────────────────────────────────────────

    #[OA\Get(
        path: '/api/v1/admin/comments/pending',
        summary: 'List all pending comments (ROLE_ADMIN only)',
        security: [['bearerAuth' => []]],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Array of pending comment objects',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(property: 'data', type: 'array', items: new OA\Items(type: 'object')),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 403, description: 'Forbidden — ROLE_ADMIN required'),
        ]
    )]
    #[IsGranted('ROLE_ADMIN')]
    #[Route('/api/v1/admin/comments/pending', methods: ['GET'])]
    public function pendingComments(Request $request): JsonResponse
    {
        $page  = max(1, (int) $request->query->get('page', 1));
        $limit = min(100, max(1, (int) $request->query->get('limit', 50)));

        $comments = array_map(
            fn(Comment $c) => $c->toArray(),
            $this->commentRepo->findPending($limit, ($page - 1) * $limit)
        );
        $total = $this->commentRepo->countPending();

        return $this->success([
            'data' => $comments,
            'meta' => [
                'totalRecords' => $total,
                'totalPages'   => (int) ceil($total / $limit),
                'currentPage'  => $page,
            ],
        ]);
    }

    #[OA\Post(
        path: '/api/v1/admin/comment/{uuid}/approve',
        summary: 'Approve a comment (ROLE_ADMIN only)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Comment approved',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(property: 'data', type: 'object', description: 'Comment object'),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 403, description: 'Forbidden — ROLE_ADMIN required'),
            new OA\Response(response: 404, description: 'Comment not found'),
        ]
    )]
    #[IsGranted('ROLE_ADMIN')]
    #[Route('/api/v1/admin/comment/{uuid}/approve', methods: ['POST'])]
    public function approveComment(string $uuid): JsonResponse
    {
        $comment = $this->commentRepo->findByUuid($uuid);
        if ($comment === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نظر یافت نشد', 404);
        }
        $comment->approve();
        $this->commentRepo->save($comment);
        return $this->success(['data' => $comment->toArray()]);
    }

    #[OA\Post(
        path: '/api/v1/admin/comment/{uuid}/reject',
        summary: 'Reject a comment (ROLE_ADMIN only)',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'uuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Comment rejected',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(property: 'data', type: 'object', description: 'Comment object'),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 403, description: 'Forbidden — ROLE_ADMIN required'),
            new OA\Response(response: 404, description: 'Comment not found'),
        ]
    )]
    #[IsGranted('ROLE_ADMIN')]
    #[Route('/api/v1/admin/comment/{uuid}/reject', methods: ['POST'])]
    public function rejectComment(string $uuid): JsonResponse
    {
        $comment = $this->commentRepo->findByUuid($uuid);
        if ($comment === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نظر یافت نشد', 404);
        }
        $comment->reject();
        $this->commentRepo->save($comment);
        return $this->success(['data' => $comment->toArray()]);
    }

    // ── Likes (toggle) ────────────────────────────────────────────────────────

    #[OA\Post(
        path: '/api/v1/like/{commentUuid}',
        summary: 'Toggle like on a comment',
        security: [['bearerAuth' => []]],
        parameters: [
            new OA\Parameter(name: 'commentUuid', in: 'path', required: true, schema: new OA\Schema(type: 'string')),
        ],
        responses: [
            new OA\Response(
                response: 200,
                description: 'Like removed (unliked)',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(
                            property: 'data',
                            properties: [
                                new OA\Property(property: 'liked', type: 'boolean', example: false),
                                new OA\Property(property: 'likes', type: 'integer'),
                            ],
                            type: 'object'
                        ),
                    ]
                )
            ),
            new OA\Response(
                response: 201,
                description: 'Like added',
                content: new OA\JsonContent(
                    properties: [
                        new OA\Property(property: 'success', type: 'boolean', example: true),
                        new OA\Property(
                            property: 'data',
                            properties: [
                                new OA\Property(property: 'liked', type: 'boolean', example: true),
                                new OA\Property(property: 'likes', type: 'integer'),
                            ],
                            type: 'object'
                        ),
                    ]
                )
            ),
            new OA\Response(response: 401, description: 'Unauthorized'),
            new OA\Response(response: 404, description: 'Comment not found'),
        ]
    )]
    #[IsGranted('IS_AUTHENTICATED_FULLY')]
    #[Route('/api/v1/like/{commentUuid}', methods: ['POST'])]
    public function toggleLike(string $commentUuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $comment = $this->commentRepo->findByUuid($commentUuid);
        if ($comment === null) {
            return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'نظر یافت نشد', 404);
        }

        $data  = json_decode($request->getContent(), true) ?? [];
        $value = ((int) ($data['value'] ?? 1)) >= 0 ? 1 : -1;

        $existing = $this->likeRepo->findByUserAndComment($user, $comment);
        if ($existing !== null) {
            if ($existing->getValue() === $value) {
                $this->likeRepo->remove($existing);
            } else {
                $existing->setValue($value);
                $this->likeRepo->save($existing);
            }
        } else {
            $this->likeRepo->save(new Like($user, $comment, $value));
        }

        $counts  = $this->likeRepo->countByComment($comment);
        $current = $this->likeRepo->findByUserAndComment($user, $comment);

        return $this->success([
            'like_count'    => $counts['like_count'],
            'dislike_count' => $counts['dislike_count'],
            'current_user_like' => [
                'like'    => $current !== null && $current->getValue() >= 0,
                'dislike' => $current !== null && $current->getValue() < 0,
            ],
        ]);
    }
}
