<?php

namespace App\Services;

use App\Models\UserModel;
use Config\Tieasy;

/**
 * Profile onboarding and Settings field handling.
 * Identity always comes from the authenticated session user, never from POST.
 */
class ProfileService
{
    public const MSG_NAME_BLANK     = 'Enter a profile name or leave this field blank.';
    public const MSG_NAME_LENGTH    = 'Profile name must be 100 characters or fewer.';
    public const MSG_NAME_CHARS     = 'Profile name can include letters, numbers, and normal punctuation only.';
    public const MSG_OCC_BLANK      = 'Enter an occupation or leave this field blank.';
    public const MSG_OCC_LENGTH     = 'Occupation must be 100 characters or fewer.';
    public const MSG_OCC_CHARS      = 'Occupation can include letters, numbers, and normal punctuation only.';
    public const MSG_AGE            = 'Please choose a valid age group.';
    public const MSG_GENDER         = 'Please choose a valid gender.';
    public const MSG_PHONE_LENGTH   = 'Phone number must be 20 characters or fewer.';
    public const MSG_PHONE_INVALID  = 'Enter a valid phone number using digits and + - ( ).';
    public const MSG_SAVE_FAIL      = 'We could not save your profile. Please try again.';

    public const PHONE_MAX = 20;
    public const TEXT_MAX  = 100;

    public function __construct(
        private readonly UserModel $users = new UserModel(),
        private readonly Tieasy $config = new Tieasy(),
    ) {
    }

    /**
     * @return array<string, mixed>|null
     */
    public function userFromSession(): ?array
    {
        $id = (int) session()->get('user_id');
        if ($id < 1) {
            return null;
        }

        $user = $this->users->find($id);

        return $user === null ? null : $user;
    }

    /**
     * Pull editable fields from a request payload. Email and user_id are ignored.
     *
     * @param array<string, mixed> $post
     *
     * @return array{
     *     profile_name: string,
     *     age_group: string,
     *     gender: string,
     *     occupation: string,
     *     phone: string
     * }
     */
    public function collect(array $post): array
    {
        return [
            'profile_name' => $this->rawString($post['profile_name'] ?? ''),
            'age_group'    => $this->rawString($post['age_group'] ?? ''),
            'gender'       => $this->rawString($post['gender'] ?? ''),
            'occupation'   => $this->rawString($post['occupation'] ?? ''),
            'phone'        => $this->rawString($post['phone'] ?? ''),
        ];
    }

    /**
     * @param array<string, string> $raw
     *
     * @return array<string, string>
     */
    public function validate(array $raw): array
    {
        $errors = [];

        $nameError = $this->validateOptionalText($raw['profile_name'], 'name');
        if ($nameError !== null) {
            $errors['profile_name'] = $nameError;
        }

        $age = trim($raw['age_group']);
        if ($age !== '' && ! in_array($age, $this->config->ageGroups, true)) {
            $errors['age_group'] = self::MSG_AGE;
        }

        $gender = trim($raw['gender']);
        if ($gender !== '' && ! in_array($gender, $this->config->genders, true)) {
            $errors['gender'] = self::MSG_GENDER;
        }

        $occError = $this->validateOptionalText($raw['occupation'], 'occupation');
        if ($occError !== null) {
            $errors['occupation'] = $occError;
        }

        $phoneError = $this->validatePhone($raw['phone']);
        if ($phoneError !== null) {
            $errors['phone'] = $phoneError;
        }

        return $errors;
    }

    /**
     * @param array<string, string> $raw
     *
     * @return array{
     *     profile_name: string|null,
     *     age_group: string|null,
     *     gender: string|null,
     *     occupation: string|null,
     *     phone: string|null
     * }
     */
    public function normalized(array $raw): array
    {
        $name = trim($raw['profile_name']);
        $occ  = trim($raw['occupation']);
        $age  = trim($raw['age_group']);
        $gen  = trim($raw['gender']);
        $tel  = trim($raw['phone']);

        return [
            'profile_name' => $name === '' ? null : $name,
            'age_group'    => $age === '' ? null : $age,
            'gender'       => $gen === '' ? null : $gen,
            'occupation'   => $occ === '' ? null : $occ,
            'phone'        => $tel === '' ? null : $tel,
        ];
    }

    /**
     * @param array<string, string|null> $fields
     */
    public function save(int $userId, array $fields, bool $completeOnboarding = false): bool
    {
        $payload = [
            'profile_name' => $fields['profile_name'],
            'age_group'    => $fields['age_group'],
            'gender'       => $fields['gender'],
            'occupation'   => $fields['occupation'],
            'phone'        => $fields['phone'],
        ];

        if ($completeOnboarding) {
            $payload['profile_onboarding_completed'] = 1;
        }

        try {
            $this->users->skipValidation(true)->update($userId, $payload);
        } catch (\Throwable $exception) {
            log_message('error', 'Profile save failed: {0}', [$exception->getMessage()]);

            return false;
        }

        if ($completeOnboarding) {
            session()->set('profile_onboarding_completed', 1);
        }

        return true;
    }

    public function markOnboardingComplete(int $userId): bool
    {
        try {
            $this->users->skipValidation(true)->update($userId, [
                'profile_onboarding_completed' => 1,
            ]);
        } catch (\Throwable $exception) {
            log_message('error', 'Onboarding complete failed: {0}', [$exception->getMessage()]);

            return false;
        }

        session()->set('profile_onboarding_completed', 1);

        return true;
    }

    /**
     * Display values for a form (DB row or posted raw input).
     *
     * @param array<string, mixed> $source
     *
     * @return array<string, string>
     */
    public function formValues(array $source): array
    {
        return [
            'profile_name' => (string) ($source['profile_name'] ?? ''),
            'age_group'    => (string) ($source['age_group'] ?? ''),
            'gender'       => (string) ($source['gender'] ?? ''),
            'occupation'   => (string) ($source['occupation'] ?? ''),
            'phone'        => (string) ($source['phone'] ?? ''),
        ];
    }

    private function rawString(mixed $value): string
    {
        if (! is_string($value) && ! is_numeric($value)) {
            return '';
        }

        return (string) $value;
    }

    private function validateOptionalText(string $raw, string $kind): ?string
    {
        $blankMsg  = $kind === 'occupation' ? self::MSG_OCC_BLANK : self::MSG_NAME_BLANK;
        $lengthMsg = $kind === 'occupation' ? self::MSG_OCC_LENGTH : self::MSG_NAME_LENGTH;
        $charsMsg  = $kind === 'occupation' ? self::MSG_OCC_CHARS : self::MSG_NAME_CHARS;

        if ($raw !== '' && trim($raw) === '') {
            return $blankMsg;
        }

        $value = trim($raw);
        if ($value === '') {
            return null;
        }

        if (mb_strlen($value) > self::TEXT_MAX) {
            return $lengthMsg;
        }

        if (! $this->isAllowedText($value)) {
            return $charsMsg;
        }

        return null;
    }

    private function validatePhone(string $raw): ?string
    {
        if ($raw !== '' && trim($raw) === '') {
            return self::MSG_PHONE_INVALID;
        }

        $phone = trim($raw);
        if ($phone === '') {
            return null;
        }

        if (mb_strlen($phone) > self::PHONE_MAX) {
            return self::MSG_PHONE_LENGTH;
        }

        if (preg_match('/^\+?[0-9 ()\-]+$/', $phone) !== 1) {
            return self::MSG_PHONE_INVALID;
        }

        if (preg_match('/\d/', $phone) !== 1) {
            return self::MSG_PHONE_INVALID;
        }

        return null;
    }

    private function isAllowedText(string $value): bool
    {
        if (preg_match('/\p{C}/u', $value) === 1) {
            return false;
        }

        return preg_match('/^[\p{L}\p{M}\p{N}\p{P}\p{Zs}]+$/u', $value) === 1;
    }
}
