<?php

declare(strict_types=1);

/**
 * Reward Loyalty - Proprietary Software
 * Copyright (c) 2026 NowSquare. All rights reserved.
 * See LICENSE file for terms.
 *
 * Purpose:
 * Manages anonymous member sessions and device binding.
 * Implements the "Brawl Stars" model for frictionless onboarding.
 *
 * Philosophy:
 * "Play first, account later" — Users can immediately use loyalty features
 * without the friction of registration. Email linking becomes optional,
 * primarily for cross-device sync and receiving notifications.
 *
 * @see App\Models\Member
 */

namespace App\Services\Member;

use App\Models\Member;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class AnonymousMemberService
{
    /**
     * Initialize or retrieve a member session based on device UUID.
     * Creates a new anonymous member if none exists for this device.
     *
     * @param  string  $deviceUuid  UUID from client localStorage
     * @param  string|null  $timeZone  Browser timezone (e.g., "Europe/Amsterdam")
     * @return Member The member associated with this device
     */
    public function initSession(string $deviceUuid, ?string $timeZone = null): Member
    {
        // Try to find existing member by device UUID
        $member = Member::findByDeviceUuid($deviceUuid);

        if ($member) {
            // Update timezone if provided and different
            if ($timeZone && $member->time_zone !== $timeZone) {
                $member->update(['time_zone' => $timeZone]);
            }
            return $member;
        }

        // Create new anonymous member
        return $this->createAnonymousMember($deviceUuid, $timeZone);
    }

    /**
     * Create a new anonymous member bound to a device.
     *
     * Note: unique_identifier is auto-generated by HasIdentifier trait (format: 512-102-314-864)
     *
     * @param  string  $deviceUuid  UUID from client localStorage
     * @param  string|null  $timeZone  Browser timezone (e.g., "Europe/Amsterdam") — null if not yet known
     * @return Member The newly created member
     */
    public function createAnonymousMember(string $deviceUuid, ?string $timeZone = null): Member
    {
        $codeLength = (int) config('default.anonymous_member_code_length', 4);
        $deviceCode = Member::generateDeviceCode($codeLength);

        // Determine defaults
        $defaultCurrency = config('default.currency', 'USD');

        return Member::create([
            'device_uuid' => $deviceUuid,
            'device_code' => $deviceCode,
            // unique_identifier auto-generated by HasIdentifier trait
            'name' => $deviceCode, // Just the code (e.g., "R245") — space-efficient for UI
            'email' => null, // Anonymous = no email
            'is_active' => true,
            'accepts_emails' => false, // Can't send emails without address
            'locale' => app()->getLocale(),
            'currency' => $defaultCurrency, // Required by I18nMiddleware
            'time_zone' => $timeZone, // Only set if browser provided it; null = update on next request
        ]);
    }

    /**
     * Switch a device to an existing member account.
     * Used when a user enters a different device code to "login".
     *
     * @param  string  $newDeviceCode  The code to switch to (e.g., "4K7X")
     * @param  string  $deviceUuid  Current device UUID from localStorage
     * @return array{success: bool, member: ?Member, message: ?string}
     */
    public function switchToMember(string $newDeviceCode, string $deviceUuid): array
    {
        $targetMember = Member::findByDeviceCode($newDeviceCode);

        if (! $targetMember) {
            return [
                'success' => false,
                'member' => null,
                'message' => trans('common.code_not_found'),
            ];
        }

        if (! $targetMember->is_active) {
            return [
                'success' => false,
                'member' => null,
                'message' => trans('common.account_inactive'),
            ];
        }

        // Update the target member's device UUID to this device
        // This effectively "logs in" by taking over the session
        $targetMember->update(['device_uuid' => $deviceUuid]);

        return [
            'success' => true,
            'member' => $targetMember->fresh(),
            'device_uuid' => $deviceUuid, // Return for frontend localStorage update
            'message' => null,
        ];
    }

    /**
     * Link an email to an anonymous member (upgrade to registered).
     *
     * @param  Member  $member  The anonymous member
     * @param  string  $email  Email address to link
     * @param  string|null  $name  Optional name
     * @return Member Updated member
     *
     * @throws ValidationException
     */
    public function linkEmail(Member $member, string $email, ?string $name = null): Member
    {
        $email = strtolower(trim($email));

        // Check if email is already taken by another member
        $existing = Member::where('email', $email)->first();

        if ($existing && $existing->id !== $member->id) {
            throw ValidationException::withMessages([
                'email' => [trans('validation.unique', ['attribute' => 'email'])],
            ]);
        }

        $member->update([
            'email' => $email,
            'name' => $name ?? $member->name,
            'accepts_emails' => true, // Default to opted-in when linking email
        ]);

        return $member->refresh();
    }

    /**
     * Unlink email from a registered member (downgrade to anonymous).
     * Use with caution — member loses notifications and cross-device sync.
     *
     * @param  Member  $member  The registered member
     * @return Member Updated anonymous member
     */
    public function unlinkEmail(Member $member): Member
    {
        $member->update([
            'email' => null,
            'accepts_emails' => false,
        ]);

        return $member->refresh();
    }

    /**
     * Check if anonymous members feature is enabled.
     */
    public function isEnabled(): bool
    {
        return (bool) config('default.anonymous_members_enabled', true);
    }

    /**
     * Get the configured device code length.
     */
    public function getCodeLength(): int
    {
        return (int) config('default.anonymous_member_code_length', 4);
    }

    /**
     * Get the timestamp when anonymous members were forced to log out.
     * Returns null if no forced logout has been triggered.
     */
    public function getForcedLogoutTimestamp(): ?int
    {
        $timestamp = config('default.anonymous_forced_logout_at');
        return $timestamp ? (int) $timestamp : null;
    }
}
