<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Artwork;
use App\Models\Group;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class ArtworkAttributionService
{
    public function __construct(
        private readonly GroupMembershipService $groupMembers,
        private readonly GroupService $groups,
    ) {
    }

    public function apply(Artwork $artwork, User $actor, array $attributes, bool $requireDirectPublish = true): Artwork
    {
        $previousGroupId = (int) ($artwork->group_id ?? 0);
        $group = $this->resolveGroup($actor, $attributes, $requireDirectPublish);
        $allowedContributorIds = $group ? $this->groupMembers->activeContributorIds($group) : [(int) $actor->id];

        $primaryAuthorId = $this->resolvePrimaryAuthorId($actor, $attributes, $allowedContributorIds);
        $contributorCredits = $this->normalizeContributorCredits($attributes, $allowedContributorIds, $primaryAuthorId);

        DB::transaction(function () use ($artwork, $actor, $group, $primaryAuthorId, $contributorCredits): void {
            $artwork->group()->associate($group);
            $artwork->uploadedBy()->associate($actor);
            $artwork->primaryAuthor()->associate(User::query()->findOrFail($primaryAuthorId));
            $artwork->published_as_type = $group ? Artwork::PUBLISHED_AS_GROUP : Artwork::PUBLISHED_AS_USER;
            $artwork->published_as_id = $group?->id ?: (int) $artwork->user_id;
            $artwork->save();

            $artwork->contributors()->delete();

            foreach ($contributorCredits as $index => $contributorCredit) {
                $artwork->contributors()->create([
                    'user_id' => $contributorCredit['user_id'],
                    'credit_role' => $contributorCredit['credit_role'],
                    'is_primary' => $contributorCredit['is_primary'],
                    'sort_order' => $index,
                ]);
            }
        });

        $artwork->loadMissing(['group.members', 'primaryAuthor.profile', 'contributors.user.profile', 'uploadedBy.profile']);

        $newGroupId = (int) ($artwork->group_id ?? 0);
        if ($previousGroupId > 0 && $previousGroupId !== $newGroupId) {
            $previousGroup = Group::query()->find($previousGroupId);
            if ($previousGroup) {
                $this->groups->syncArtworkCount($previousGroup);
            }
        }

        if ($newGroupId > 0) {
            $this->groups->syncArtworkCount($artwork->group);
        }

        return $artwork;
    }

    private function resolveGroup(User $actor, array $attributes, bool $requireDirectPublish = true): ?Group
    {
        $groupIdentifier = $attributes['group'] ?? $attributes['group_id'] ?? null;
        if ($groupIdentifier === null || $groupIdentifier === '') {
            return null;
        }

        $group = is_numeric($groupIdentifier)
            ? Group::query()->with('members')->findOrFail((int) $groupIdentifier)
            : Group::query()->with('members')->where('slug', (string) $groupIdentifier)->firstOrFail();

        $canUseGroup = $requireDirectPublish
            ? $group->canPublishArtworks($actor)
            : $group->canCreateArtworkDrafts($actor);

        if (! $canUseGroup && ! $actor->isAdmin()) {
            throw ValidationException::withMessages([
                'group' => $requireDirectPublish
                    ? 'You are not allowed to publish as this group.'
                    : 'You are not allowed to submit artwork for this group.',
            ]);
        }

        return $group;
    }

    private function resolvePrimaryAuthorId(User $actor, array $attributes, array $allowedContributorIds): int
    {
        $primaryAuthorId = isset($attributes['primary_author_user_id']) && is_numeric($attributes['primary_author_user_id'])
            ? (int) $attributes['primary_author_user_id']
            : (int) $actor->id;

        if (! in_array($primaryAuthorId, $allowedContributorIds, true)) {
            throw ValidationException::withMessages([
                'primary_author_user_id' => 'The selected primary author is not available for this publishing context.',
            ]);
        }

        return $primaryAuthorId;
    }

    private function normalizeContributorCredits(array $attributes, array $allowedContributorIds, int $primaryAuthorId): array
    {
        $structuredCredits = collect($attributes['contributor_credits'] ?? [])
            ->filter(fn ($credit): bool => is_array($credit) && is_numeric($credit['user_id'] ?? null))
            ->map(function (array $credit): array {
                $creditRole = trim((string) ($credit['credit_role'] ?? ''));

                return [
                    'user_id' => (int) $credit['user_id'],
                    'credit_role' => $creditRole !== '' ? $creditRole : null,
                    'is_primary' => (bool) ($credit['is_primary'] ?? false),
                ];
            })
            ->filter(fn (array $credit): bool => in_array($credit['user_id'], $allowedContributorIds, true))
            ->reject(fn (array $credit): bool => $credit['user_id'] === $primaryAuthorId)
            ->unique('user_id')
            ->values();

        if ($structuredCredits->isEmpty()) {
            $structuredCredits = collect($attributes['contributor_user_ids'] ?? [])
                ->filter(fn ($id): bool => is_numeric($id))
                ->map(fn ($id): array => [
                    'user_id' => (int) $id,
                    'credit_role' => null,
                    'is_primary' => false,
                ])
                ->filter(fn (array $credit): bool => in_array($credit['user_id'], $allowedContributorIds, true))
                ->reject(fn (array $credit): bool => $credit['user_id'] === $primaryAuthorId)
                ->unique('user_id')
                ->values();
        }

        if ($structuredCredits->where('is_primary', true)->count() > 1) {
            throw ValidationException::withMessages([
                'contributor_credits' => 'Only one contributor can be marked as the lead supporting credit.',
            ]);
        }

        return $structuredCredits->all();
    }
}