<?php

namespace App\UserProfile\Controller;

use App\Auth\Entity\User;
use App\Auth\Repository\UserRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Controller\BaseController;
use App\Shared\Service\FileValidatorService;
use App\Shared\Service\InputValidator;
use App\UserProfile\Entity\UserProfile;
use App\UserProfile\Repository\UserProfileRepository;
use Symfony\Component\Uid\Uuid;
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;
use OpenApi\Attributes as OA;

#[OA\Tag(name: 'User Profile')]
#[IsGranted('IS_AUTHENTICATED_FULLY')]
class UserProfileController extends BaseController
{
    public function __construct(
        private readonly UserProfileRepository $repository,
        private readonly UserRepository        $userRepository,
        private readonly FileValidatorService  $fileValidator,
        private readonly string                $projectDir,
    ) {}

    #[OA\Post(
        path: '/api/v1/user-profile/avatar',
        summary: 'Upload the current user\'s profile avatar',
        security: [['bearerAuth' => []]],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\MediaType(
                mediaType: 'application/octet-stream',
                schema: new OA\Schema(type: 'string', format: 'binary')
            )
        ),
        responses: [
            new OA\Response(response: 200, description: 'Avatar uploaded'),
            new OA\Response(response: 422, description: 'Invalid file'),
        ]
    )]
    #[Route('/api/v1/user-profile/avatar', methods: ['POST'])]
    public function uploadAvatar(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $profile = $this->repository->findByUser($user) ?? new UserProfile($user);

        $content     = $request->getContent();
        if ($content === '') {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'فایلی ارسال نشده است', 422);
        }
        $disposition = $request->headers->get('Content-Disposition', '');
        preg_match('/filename=["\']?([^"\';\s]+)["\']?/i', $disposition, $m);
        $filename = $m[1] ?? 'avatar.jpg';

        $tmpPath = sys_get_temp_dir() . '/' . uniqid('avatar_', true);
        file_put_contents($tmpPath, $content);

        try {
            $safeFilename = $this->fileValidator->sanitizeFilename($filename);
            $this->fileValidator->detectMimeType($tmpPath);

            $year = date('Y'); $month = date('m');
            $dir  = $this->projectDir . '/public/uploads/avatars/' . $year . '-' . $month;
            if (!is_dir($dir)) mkdir($dir, 0755, true);

            $storedName = uniqid('', true) . '_' . $safeFilename;
            rename($tmpPath, $dir . '/' . $storedName);

            $url = '/uploads/avatars/' . $year . '-' . $month . '/' . $storedName;
            $profile->setAvatar($url);
            $this->repository->save($profile);

            return $this->success(['avatar' => $url, 'url' => $url]);
        } catch (\Throwable $e) {
            if (file_exists($tmpPath)) unlink($tmpPath);
            return $this->error(ErrorCodes::ERR_VALIDATION_001, $e->getMessage(), 422);
        }
    }

    #[Route('/api/v1/user-profile', methods: ['POST'])]
    public function create(Request $request, #[CurrentUser] User $user): JsonResponse
    {
        if ($this->repository->findByUser($user) !== null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'پروفایل قبلاً ایجاد شده است', 409);
        }

        $data    = json_decode($request->getContent(), true) ?? [];

        if ($error = $this->guardNationalCode($data, null)) {
            return $error;
        }

        $profile = new UserProfile($user);
        $this->hydrate($profile, $data);
        $this->repository->save($profile);

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

    #[Route('/api/v1/user-profile/{uuid}', methods: ['GET'])]
    public function show(string $uuid, #[CurrentUser] User $user): JsonResponse
    {
        // {uuid} may be a profile uuid or a user uuid; a user with no profile yet
        // gets an empty one created so the client always has an editable profile.
        $profile = $this->resolveProfile($uuid, $user, true);

        if ($profile === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پروفایل یافت نشد', 404);
        }

        if (!$this->canAccess($profile, $user)) {
            return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
        }

        return $this->success(['data' => $profile->toArray()]);
    }

    #[Route('/api/v1/user-profile/{uuid}', methods: ['PATCH'])]
    public function update(string $uuid, Request $request, #[CurrentUser] User $user): JsonResponse
    {
        $profile = $this->resolveProfile($uuid, $user, true);

        if ($profile === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پروفایل یافت نشد', 404);
        }

        if (!$this->canAccess($profile, $user)) {
            return $this->error(ErrorCodes::ERR_AUTH_006, 'دسترسی ممنوع', 403);
        }

        $data = json_decode($request->getContent(), true) ?? [];

        if ($error = $this->guardNationalCode($data, $profile)) {
            return $error;
        }

        $this->hydrate($profile, $data);
        $this->repository->save($profile);

        return $this->success(['data' => $profile->toArray()]);
    }

    #[Route('/api/v1/user-profile/{uuid}', methods: ['DELETE'])]
    #[IsGranted('ROLE_ADMIN')]
    public function delete(string $uuid): JsonResponse
    {
        $profile = $this->repository->findByUuid($uuid);

        if ($profile === null) {
            return $this->error(ErrorCodes::ERR_VALIDATION_002, 'پروفایل یافت نشد', 404);
        }

        $this->repository->remove($profile);

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

    /**
     * Resolve a profile from a uuid that may be the profile's own uuid or the
     * owning user's uuid. When $createIfMissing is true and the uuid belongs to
     * the **current user** without a profile, an empty profile is created and
     * persisted. An admin viewing another user's missing profile gets null (no
     * record is created as a side effect of reading).
     */
    private function resolveProfile(string $uuid, User $currentUser, bool $createIfMissing): ?UserProfile
    {
        $profile = $this->repository->findByUuid($uuid);
        if ($profile !== null) {
            return $profile;
        }

        $targetUser = $this->userRepository->findByUuid($uuid);
        if ($targetUser === null) {
            return null;
        }

        $profile = $this->repository->findByUser($targetUser);
        if ($profile !== null) {
            return $profile;
        }

        // Only lazy-create for the user's own profile, never as a side effect of
        // an admin reading someone else's.
        if (!$createIfMissing || $targetUser->getId() !== $currentUser->getId()) {
            return null;
        }

        $profile = new UserProfile($targetUser);
        $this->repository->save($profile);

        return $profile;
    }

    private function canAccess(UserProfile $profile, User $currentUser): bool
    {
        return $profile->getUser()->getId() === $currentUser->getId()
            || $currentUser->hasRole('ROLE_ADMIN');
    }

    /**
     * Normalize, format-validate and uniqueness-check national_code in $data.
     * Mutates $data['national_code'] to the latin-digit form. Returns an error
     * response if invalid or already taken by another profile, otherwise null.
     */
    private function guardNationalCode(array &$data, ?UserProfile $current): ?JsonResponse
    {
        if (!array_key_exists('national_code', $data) || $data['national_code'] === null || $data['national_code'] === '') {
            return null;
        }

        $code = InputValidator::toEnglishDigits((string) $data['national_code']);
        if (!InputValidator::isValidIranNationalCode($code)) {
            return $this->error(ErrorCodes::ERR_VALIDATION_001, 'کد ملی نامعتبر است', 422, 'national_code');
        }
        $data['national_code'] = $code;

        $existing = $this->repository->findOneByNationalCode($code);
        if ($existing !== null && ($current === null || $existing->getUuid() !== $current->getUuid())) {
            $masked = InputValidator::maskMobile($existing->getUser()->getMobileNumber());
            return $this->error(
                ErrorCodes::ERR_PROFILE_NATIONAL_CODE_TAKEN,
                "این کد ملی قبلاً با شماره {$masked} ثبت شده است",
                409,
                'national_code'
            );
        }

        return null;
    }

    private function hydrate(UserProfile $profile, array $data): void
    {
        if (array_key_exists('name', $data))           $profile->setLabel($data['name']);
        if (array_key_exists('label', $data))          $profile->setLabel($data['label']);
        if (array_key_exists('family', $data))         $profile->setFamily($data['family']);
        if (array_key_exists('fathers_name', $data))   $profile->setFathersName($data['fathers_name']);
        if (array_key_exists('national_code', $data))  $profile->setNationalCode($data['national_code']);
        if (array_key_exists('gender', $data))         $profile->setGender($data['gender']);
        if (array_key_exists('blood_type', $data))     $profile->setBloodType($data['blood_type']);
        if (array_key_exists('marital_status', $data)) $profile->setMaritalStatus($data['marital_status']);
        if (array_key_exists('education', $data))      $profile->setEducation($data['education']);
        if (array_key_exists('job', $data))            $profile->setJob($data['job']);
        if (array_key_exists('address', $data))        $profile->setAddress($data['address']);
        if (array_key_exists('home_phone', $data))     $profile->setHomePhone($data['home_phone']);
        if (array_key_exists('work_phone', $data))     $profile->setWorkPhone($data['work_phone']);
        if (array_key_exists('insurance_id', $data))   $profile->setInsuranceId($data['insurance_id']);
        if (array_key_exists('description', $data))    $profile->setDescription(
            is_array($data['description']) ? ($data['description'][0]['value'] ?? null) : $data['description']
        );
        if (array_key_exists('sharing_with_user', $data)) $profile->setSharingWithUser((bool) $data['sharing_with_user']);

        // birthday / date_of_birth: Unix timestamp (frontend converts Jalali↔Unix
        // via changeDateType). `birthday` takes priority; `date_of_birth` is only a
        // fallback so a stray `date_of_birth: null` in the payload can't wipe it.
        if (array_key_exists('birthday', $data)) {
            $b = $data['birthday'];
            $profile->setDateOfBirth(($b === null || $b === '') ? null : (int) $b);
        } elseif (array_key_exists('date_of_birth', $data)) {
            $b = $data['date_of_birth'];
            $profile->setDateOfBirth(($b === null || $b === '') ? null : (int) $b);
        }

        // Insurance references (category IDs)
        if (array_key_exists('basic_insurance', $data)) {
            $id = is_array($data['basic_insurance']) ? ($data['basic_insurance'][0] ?? null) : $data['basic_insurance'];
            $profile->setBasicInsuranceId($id !== null ? (int) $id : null);
        }
        if (array_key_exists('supplementary_insurance', $data)) {
            $id = is_array($data['supplementary_insurance'])
                ? ($data['supplementary_insurance'][0] ?? null)
                : $data['supplementary_insurance'];
            $profile->setSupplementaryInsuranceId($id !== null ? (int) $id : null);
        }

        // Medical history JSON
        if (array_key_exists('other', $data)) {
            $other = is_array($data['other']) && isset($data['other'][0])
                ? $data['other'][0]
                : $data['other'];
            $profile->setOther($other);
        }

        // Keep the owning user's display name (real_name) in sync with the profile,
        // so admin user listings and anywhere reading real_name show a name.
        if (array_key_exists('name', $data) || array_key_exists('label', $data) || array_key_exists('family', $data)) {
            $fullName = trim(implode(' ', array_filter([$profile->getLabel(), $profile->getFamily()])));
            $profile->getUser()->setRealName($fullName !== '' ? $fullName : null);
        }
    }
}
