<?php

namespace App\Services\Users;

use App\Models\Role;
use App\Models\User;
use App\Models\UserRole;
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

class UserManagementService
{
    public function __construct(private UserEventService $eventService)
    {
    }

    public function create(array $payload, ?int $actorId = null): array
    {
        $actorId = $actorId ?? (auth()->id() ?? null);

        try {
            $user = DB::transaction(function () use ($payload, $actorId) {
                $data = $this->normalizeForCreate($payload);
                $user = User::query()->create($data);

                UserRole::query()->create([
                    'user_id' => $user->id,
                    'role_id' => (int) $payload['role_id'],
                    'updated_by' => $actorId,
                ]);

                return $user;
            });
        } catch (\Throwable $e) {
            return ['ok' => false, 'message' => 'Failed to create user.'];
        }

        $this->eventService->handleNewAccountAdded($user->toArray());

        return ['ok' => true, 'user' => $user];
    }

    public function update(int $userId, array $payload): array
    {
        $user = User::query()->find($userId);
        if (!$user) {
            return ['ok' => false, 'message' => 'User not found.'];
        }

        $data = $this->normalizeForUpdate($payload);
        if (empty($data)) {
            return ['ok' => false, 'message' => 'No changes requested.'];
        }

        try {
            $user->fill($data);
            $user->save();
        } catch (\Throwable $e) {
            return ['ok' => false, 'message' => 'Failed to update user.'];
        }

        return ['ok' => true, 'user' => $user];
    }

    public function delete(int $userId): array
    {
        $user = User::query()->find($userId);
        if (!$user) {
            return ['ok' => false, 'message' => 'User not found.'];
        }

        try {
            DB::transaction(function () use ($userId, $user) {
                UserRole::query()->where('user_id', $userId)->forceDelete();
                $user->delete();
            });
        } catch (\Throwable $e) {
            return ['ok' => false, 'message' => 'Failed to delete user.'];
        }

        return ['ok' => true];
    }

    public function assignRole(int $userId, int $roleId, ?int $actorId = null): bool
    {
        $actorId = $actorId ?? (auth()->id() ?? null);

        if (!User::query()->whereKey($userId)->exists()) {
            return false;
        }

        if (!Role::query()->whereKey($roleId)->exists()) {
            return false;
        }

        return UserRole::updateOrInsertRole($userId, $roleId, $actorId);
    }

    public function cleanupUnverifiedUsers(int $olderThanMinutes = 15): int
    {
        $cutoff = now()->subMinutes(max(1, $olderThanMinutes));

        $deleted = User::query()
            ->where('is_verified', 0)
            ->where('created_at', '<=', $cutoff)
            ->delete();

        Log::info(sprintf(
            'Deleted %d unverified users created before %s.',
            $deleted,
            $cutoff->format('Y-m-d H:i:s')
        ));

        return $deleted;
    }

    public function validateRegistrationInput(array $data): array
    {
        $rules = [
            'firstname' => ['required', 'regex:/^[a-zA-Z\\s-]+$/', 'min:2', 'max:30'],
            'lastname' => ['required', 'regex:/^[a-zA-Z\\s-]+$/', 'min:2', 'max:30'],
            'gender' => ['required', 'in:Male,Female'],
            'email' => ['required', 'email', 'max:50'],
            'confirm_email' => ['required', 'same:email'],
            'cellphone' => ['required', 'regex:/^[\\d\\s\\-\\(\\)\\.]+$/', 'min:10', 'max:20'],
            'address_street' => ['nullable', 'regex:/^[a-zA-Z0-9\\s\\-]+$/', 'max:150'],
            'apt' => ['nullable', 'regex:/^[a-zA-Z0-9\\s-]+$/', 'max:10'],
            'city' => ['required', 'regex:/^[a-zA-Z\\s]+$/', 'min:2', 'max:30'],
            'state' => ['required', 'in:CT,ME,MA,NH,NY,RI,VT'],
            'zip' => ['required', 'regex:/^\\d{5}$/'],
            'accept_school_policy' => ['required'],
        ];

        $isParent = !empty($data['is_parent']);
        $noSecondParent = !empty($data['no_second_parent_info']);

        if ($isParent && !$noSecondParent) {
            $rules += [
                'second_firstname' => ['required', 'regex:/^[a-zA-Z\\s\\-]+$/', 'min:2', 'max:30'],
                'second_lastname' => ['required', 'regex:/^[a-zA-Z\\s\\-]+$/', 'min:2', 'max:30'],
                'second_gender' => ['required', 'in:Male,Female'],
                'second_email' => ['required', 'email', 'max:50'],
                'second_cellphone' => ['required', 'regex:/^[\\d\\s\\-\\(\\)\\.]+$/', 'min:10', 'max:20'],
            ];
        }

        $validator = Validator::make($data, $rules);

        if ($validator->fails()) {
            return $validator->errors()->toArray();
        }

        return [];
    }

    private function normalizeForCreate(array $payload): array
    {
        return [
            'school_id' => $payload['school_id'] ?? null,
            'firstname' => $this->titleCase($payload['firstname'] ?? ''),
            'lastname' => $this->titleCase($payload['lastname'] ?? ''),
            'gender' => $this->normalizeGender($payload['gender'] ?? null),
            'cellphone' => (string) ($payload['cellphone'] ?? ''),
            'email' => strtolower((string) ($payload['email'] ?? '')),
            'address_street' => (string) ($payload['address_street'] ?? ''),
            'apt' => $payload['apt'] ?? null,
            'city' => $this->titleCase($payload['city'] ?? ''),
            'state' => (string) ($payload['state'] ?? ''),
            'zip' => (string) ($payload['zip'] ?? ''),
            'accept_school_policy' => (bool) ($payload['accept_school_policy'] ?? false),
            'is_verified' => isset($payload['is_verified']) ? (bool) $payload['is_verified'] : 0,
            'status' => $payload['status'] ?? 'Inactive',
            'is_suspended' => isset($payload['is_suspended']) ? (bool) $payload['is_suspended'] : 0,
            'failed_attempts' => $payload['failed_attempts'] ?? null,
            'password' => pbkdf2_hash((string) ($payload['password'] ?? '')),
            'token' => $payload['token'] ?? null,
            'account_id' => $payload['account_id'] ?? null,
            'user_type' => $payload['user_type'] ?? 'primary',
            'semester' => (string) ($payload['semester'] ?? ''),
            'school_year' => $payload['school_year'] ?? null,
            'rfid_tag' => $payload['rfid_tag'] ?? null,
            'last_failed_at' => $this->normalizeDateTime($payload['last_failed_at'] ?? null),
        ];
    }

    private function normalizeForUpdate(array $payload): array
    {
        $data = [];
        $map = [
            'school_id',
            'firstname',
            'lastname',
            'gender',
            'cellphone',
            'email',
            'address_street',
            'apt',
            'city',
            'state',
            'zip',
            'accept_school_policy',
            'user_type',
            'rfid_tag',
            'failed_attempts',
            'last_failed_at',
            'semester',
            'school_year',
            'status',
            'is_suspended',
            'is_verified',
            'token',
            'account_id',
        ];

        foreach ($map as $field) {
            if (!array_key_exists($field, $payload)) {
                continue;
            }

            $value = $payload[$field];
            switch ($field) {
                case 'firstname':
                case 'lastname':
                case 'city':
                    $data[$field] = $this->titleCase($value);
                    break;
                case 'email':
                    $data[$field] = strtolower((string) $value);
                    break;
                case 'gender':
                    $data[$field] = $this->normalizeGender($value);
                    break;
                case 'accept_school_policy':
                case 'is_suspended':
                case 'is_verified':
                    $data[$field] = (bool) $value;
                    break;
                case 'last_failed_at':
                    $data[$field] = $this->normalizeDateTime($value);
                    break;
                case 'failed_attempts':
                case 'school_id':
                    $data[$field] = $value === '' ? null : $value;
                    break;
                default:
                    $data[$field] = $value;
                    break;
            }
        }

        return $data;
    }

    private function normalizeGender(?string $value): ?string
    {
        $value = strtolower(trim((string) $value));
        if ($value === '') {
            return null;
        }
        if ($value === 'male') {
            return 'Male';
        }
        if ($value === 'female') {
            return 'Female';
        }
        return null;
    }

    private function titleCase(?string $value): string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return '';
        }
        return ucfirst($value);
    }

    private function normalizeDateTime($value): ?string
    {
        $value = trim((string) ($value ?? ''));
        if ($value === '') {
            return null;
        }

        try {
            return CarbonImmutable::parse($value)->format('Y-m-d H:i:s');
        } catch (\Throwable $e) {
            return null;
        }
    }
}
