<?php

namespace App\Models;

use Database\Factories\AllowedAdAccountFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class AllowedAdAccount extends Model
{
    /** @use HasFactory<AllowedAdAccountFactory> */
    use HasFactory;

    protected $fillable = [
        'platform',
        'account_id',
        'name',
        'is_active',
        'notes',
    ];

    protected function casts(): array
    {
        return [
            'is_active' => 'boolean',
        ];
    }

    /**
     * Scope query to active accounts only.
     *
     * @param  Builder<AllowedAdAccount>  $query
     */
    public function scopeActive(Builder $query): Builder
    {
        return $query->where('is_active', true);
    }

    /**
     * Scope query to a specific platform.
     *
     * @param  Builder<AllowedAdAccount>  $query
     */
    public function scopeForPlatform(Builder $query, string $platform): Builder
    {
        return $query->where('platform', strtolower(trim($platform)));
    }

    /**
     * Stands for every account on one platform.
     *
     * Only ever meaningful with a platform prefix: "meta:*" allows every Meta
     * account, and a bare "*" is deliberately not honoured, because entryFor()
     * reads an unprefixed entry as Google-or-LinkedIn, so a lone star would
     * silently open two platforms while looking like it opened one.
     */
    public const WILDCARD = '*';

    /**
     * Reduce an account ID to its canonical platform form, written the way the allow list is written.
     *
     * Meta's ad accounts are `act_` plus digits and are referred to both ways,
     * so the prefix is added when it is missing. Google's customer ids are ten
     * digits, written with or without hyphens, and never carry a prefix:
     * applying Meta's here turned 9876543210 into act_9876543210 and made every
     * Google call fail a list it could never appear in.
     *
     * Either the LinkedIn URN or the bare id can arrive here and can be
     * configured, and the trailing segment identifies the account in both.
     * Returning the URN untouched made the two spellings of one account fail
     * to match each other.
     */
    public static function normalizeAccountId(string $adAccountId, string $platform = 'meta'): string
    {
        $platform = strtolower(trim($platform));

        // The wildcard is not an account and must not be dressed up as one.
        // Meta's arm below would turn it into act_*, which matches nothing and
        // would make "meta:*" quietly mean "no accounts" instead of "all".
        if (trim($adAccountId) === self::WILDCARD) {
            return self::WILDCARD;
        }

        return match ($platform) {
            'google' => str_replace('-', '', trim($adAccountId)),
            'linkedin' => self::lastSegment(trim($adAccountId)),
            default => str_starts_with(trim($adAccountId), 'act_')
                ? trim($adAccountId)
                : 'act_'.trim($adAccountId),
        };
    }

    /**
     * Extract the trailing segment of a URN or return bare string.
     */
    private static function lastSegment(string $id): string
    {
        $parts = explode(':', $id);

        return (string) end($parts);
    }

    /**
     * Check if a specific account is actively allowed for a platform.
     */
    public static function isAllowed(string $adAccountId, string $platform = 'meta'): bool
    {
        $normalized = self::normalizeAccountId($adAccountId, $platform);

        $allowed = self::query()
            ->forPlatform($platform)
            ->active()
            ->pluck('account_id')
            ->map(fn (string $id): string => self::normalizeAccountId($id, $platform))
            ->all();

        return in_array($normalized, $allowed, true);
    }

    /**
     * Get all active allowed account IDs for a platform.
     *
     * @return list<string>
     */
    public static function allowedIds(string $platform): array
    {
        return self::query()
            ->forPlatform($platform)
            ->active()
            ->pluck('account_id')
            ->map(fn (string $id): string => self::normalizeAccountId($id, $platform))
            ->values()
            ->all();
    }
}
