<?php

declare(strict_types=1);

namespace App\Actions\Models\Wiki;

use App\Actions\ActionResult;
use App\Actions\Models\BackfillWikiAction;
use App\Actions\Models\Wiki\Anime\ExternalApi\AnilistAnimeExternalApiAction;
use App\Actions\Models\Wiki\Anime\ExternalApi\AniZipAnimeExternalApiAction;
use App\Actions\Models\Wiki\Anime\ExternalApi\JikanAnimeExternalApiAction;
use App\Actions\Models\Wiki\Anime\ExternalApi\LivechartAnimeExternalApiAction;
use App\Actions\Models\Wiki\Anime\ExternalApi\MalAnimeExternalApiAction;
use App\Concerns\Models\CanCreateStudio;
use App\Concerns\Models\HasLabel;
use App\Contracts\Actions\Models\Wiki\BackfillImages;
use App\Contracts\Actions\Models\Wiki\BackfillResources;
use App\Contracts\Actions\Models\Wiki\BackfillStudios;
use App\Contracts\Actions\Models\Wiki\BackfillSynonyms;
use App\Enums\Actions\ActionStatus;
use App\Enums\Models\Wiki\SynonymType;
use App\Models\Wiki\Anime;
use App\Models\Wiki\Synonym;
use Exception;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;

class BackfillAnimeAction extends BackfillWikiAction
{
    use CanCreateStudio;
    use HasLabel;

    final public const string STUDIOS = 'studios';
    final public const string SYNONYMS = 'synonyms';

    public function __construct(protected Anime $anime, protected array $toBackfill)
    {
        parent::__construct($anime, $toBackfill);
    }

    public function handle(): ActionResult
    {
        try {
            foreach ($this->getExternalApiActions() as $api) {
                DB::beginTransaction();

                if (
                    count($this->toBackfill[self::RESOURCES]) === 0
                    && count($this->toBackfill[self::IMAGES]) === 0
                    && ! $this->toBackfill[self::STUDIOS]
                    && ! $this->toBackfill[self::SYNONYMS]
                ) {
                    // Don't make other requests if everything is backfilled
                    Log::info("Backfill action finished for Anime {$this->getModel()->getName()}");
                    DB::rollBack();
                    break;
                }

                $response = $api->handle($this->getModel()->resources());

                if ($response instanceof BackfillResources) {
                    $this->forResources($response);
                }

                if ($response instanceof BackfillImages) {
                    $this->forImages($response);
                }

                if ($response instanceof BackfillStudios) {
                    $this->forStudios($response);
                }

                if ($response instanceof BackfillSynonyms) {
                    $this->forSynonyms($response);
                }

                DB::commit();
            }
        } catch (Exception $e) {
            Log::error($e->getMessage());

            DB::rollBack();

            throw $e;
        }

        return new ActionResult(ActionStatus::PASSED);
    }

    /**
     * Get the external API actions available for the backfill action.
     *
     * @return ExternalApiAction[]
     */
    protected function getExternalApiActions(): array
    {
        return [
            new AniZipAnimeExternalApiAction(),
            new LivechartAnimeExternalApiAction(),
            new AnilistAnimeExternalApiAction(),
            new JikanAnimeExternalApiAction(),
            new MalAnimeExternalApiAction(),
        ];
    }

    /**
     * Create the studios given the response.
     */
    protected function forStudios(ExternalApiAction&BackfillStudios $response): void
    {
        $studios = $response->getStudios();

        if (! $this->toBackfill[self::STUDIOS]) {
            return;
        }

        foreach ($studios as $studio) {
            $id = Arr::get($studio, 'id');
            $name = Arr::get($studio, 'name');

            if (blank($name) || blank($id)) {
                Log::info("Skipping empty studio of name '$name' and id '$id''");
                continue;
            }

            $studio = $this->getOrCreateStudio($name);

            Log::info("Attaching Studio of name '$name' to Anime {$this->getModel()->getName()}");
            $this->getModel()->studios()->attach($studio);

            $this->ensureStudioHasResource($studio, $response->getSite(), $id);
        }

        if ($this->getModel()->studios()->exists()) {
            $this->toBackfill[self::STUDIOS] = false;
        }
    }

    /**
     * Create the synonyms given the response.
     */
    protected function forSynonyms(ExternalApiAction&BackfillSynonyms $api): void
    {
        if (! $this->toBackfill[self::SYNONYMS]) {
            return;
        }

        $synonyms = $api->getSynonyms()
            ->reject(
                fn (string $text, int $type): bool => $type === SynonymType::OTHER->value && Str::is($text, $this->getModel()->getName(), true)
            )
            ->all();

        foreach ($synonyms as $type => $text) {
            $this->getModel()->synonyms()->create([
                Synonym::ATTRIBUTE_TEXT => $text,
                Synonym::ATTRIBUTE_TYPE => $type,
            ]);

            $this->toBackfill[self::SYNONYMS] = false;
        }
    }

    protected function getModel(): Anime
    {
        return $this->anime;
    }
}
