<?php

namespace App\Actions\Communications;

use App\Enum\Contact\MessageChannel;
use App\Enum\Contact\RecipientValidity;
use App\Models\Contact\ContactSuppression;
use App\Models\Solicitor\Solicitor;
use App\ValueObjects\Communications\RecipientSnapshot;
use App\ValueObjects\PhoneNumber;
use Illuminate\Database\Eloquent\Model;

class ValidateRecipientAction
{
    public function __invoke(MessageChannel $channel, Model $recipient, ?Solicitor $solicitor = null): RecipientSnapshot
    {
        return $channel === MessageChannel::EMAIL
            ? $this->validateEmail($recipient, $solicitor)
            : $this->validateSms($recipient, $solicitor);
    }

    private function validateEmail(Model $recipient, ?Solicitor $solicitor): RecipientSnapshot
    {
        $name = $this->nameOf($recipient);
        $raw = $recipient->email_address ?? $recipient->email ?? null;
        $email = is_object($raw) ? (string) $raw : $raw;

        if (blank($email)) {
            return new RecipientSnapshot(
                MessageChannel::EMAIL, $name, null, null,
                RecipientValidity::MISSING,
                'No email address on file.',
            );
        }

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return new RecipientSnapshot(
                MessageChannel::EMAIL, $name, $email, null,
                RecipientValidity::INVALID,
                'Email address is not a valid format.',
            );
        }

        if ($this->isSuppressed(MessageChannel::EMAIL, $email, $solicitor)) {
            return new RecipientSnapshot(
                MessageChannel::EMAIL, $name, $email, null,
                RecipientValidity::SUPPRESSED,
                'Recipient has opted out of emails.',
            );
        }

        return new RecipientSnapshot(
            MessageChannel::EMAIL, $name, $email, null,
            RecipientValidity::VALID,
        );
    }

    private function validateSms(Model $recipient, ?Solicitor $solicitor): RecipientSnapshot
    {
        $name = $this->nameOf($recipient);
        $raw = $recipient->phone_number ?? $recipient->mobile_number ?? $recipient->phone ?? null;
        $phoneStr = is_object($raw) ? (string) $raw : $raw;

        if (blank($phoneStr)) {
            return new RecipientSnapshot(
                MessageChannel::SMS, $name, null, null,
                RecipientValidity::MISSING,
                'No phone number on file.',
            );
        }

        $phone = PhoneNumber::tryFrom($phoneStr);
        if ($phone === null) {
            return new RecipientSnapshot(
                MessageChannel::SMS, $name, null, $phoneStr,
                RecipientValidity::INVALID,
                'Phone number is not a valid UK number.',
            );
        }

        if (method_exists($phone, 'isMobile') && !$phone->isMobile()) {
            return new RecipientSnapshot(
                MessageChannel::SMS, $name, null, $phone->toE164(),
                RecipientValidity::INVALID,
                'Phone number is not a UK mobile, SMS cannot be sent.',
            );
        }

        $normalized = $phone->toE164();

        if ($this->isSuppressed(MessageChannel::SMS, $normalized, $solicitor)) {
            return new RecipientSnapshot(
                MessageChannel::SMS, $name, null, $normalized,
                RecipientValidity::SUPPRESSED,
                'Recipient has opted out of SMS.',
            );
        }

        return new RecipientSnapshot(
            MessageChannel::SMS, $name, null, $normalized,
            RecipientValidity::VALID,
        );
    }

    private function nameOf(Model $recipient): ?string
    {
        // For HasName-style models, build the display name from raw columns
        // rather than reading $recipient->fullName.  The HasName trait's
        // fullName Attribute uses shouldCache() with a SET mutator that
        // splits on the first space.  Reading fullName here caches the joined
        // value; a later getAttributes()/toArray() in the request lifecycle
        // would invoke the SET on the cached value and overwrite name_first
        // and name_last in memory ("Mr John Smith" -> name_first="Mr",
        // name_last="John Smith"), which then corrupts placeholder rendering.
        if (method_exists($recipient, 'getRawOriginal')) {
            $parts = array_filter([
                $recipient->getRawOriginal('title'),
                $recipient->getRawOriginal('name_first'),
                $recipient->getRawOriginal('name_last'),
            ]);
            if (!empty($parts)) {
                return implode(' ', $parts);
            }
        }

        foreach (['name', 'display_name'] as $prop) {
            if (isset($recipient->{$prop}) && !blank($recipient->{$prop})) {
                return (string) $recipient->{$prop};
            }
        }
        return null;
    }

    private function isSuppressed(MessageChannel $channel, string $value, ?Solicitor $solicitor): bool
    {
        $query = ContactSuppression::query()
            ->where('channel', $channel->value)
            ->where('value', $value);

        // BelongsToSolicitor global scope normally applies.  If a Solicitor is passed
        // explicitly (e.g. from a webhook), narrow to it.
        if ($solicitor?->id) {
            $query->withoutGlobalScope('solicitor')->where('solicitor_id', $solicitor->id);
        }

        return $query->exists();
    }
}
