<?php

namespace App\Actions;

use App\Enums\ActivityTypeEnum;
use App\Enums\LogLevelEnum;
use App\Enums\NotificationSeverityEnum;
use App\Enums\NotificationTypeEnum;
use App\Enums\PublishStatusEnum;
use App\Events\ActionPerformed;
use App\Events\ActivityLogged;
use App\Exceptions\PublishException;
use App\Models\Article;
use App\Models\RouteArticle;
use App\Services\Notification\NotificationService;
use App\Services\Publishing\ArticlePublishingService;
use App\Services\Publishing\PublishOutcome;
use Exception;

class PublishRouteArticleAction
{
    public function __construct(
        private FetchArticleDataAction $fetchArticleData,
        private ArticlePublishingService $publishingService,
        private NotificationService $notificationService,
    ) {}

    /**
     * @throws PublishException
     */
    public function execute(RouteArticle $routeArticle): PublishOutcome
    {
        $article = $routeArticle->article;

        $routeArticle->update(['publish_status' => PublishStatusEnum::PUBLISHING]);

        try {
            $extractedData = $this->resolvePublishData($article);

            $outcome = $this->hasPublishableContent($extractedData)
                ? $this->publishingService->publishRouteArticle($routeArticle, $extractedData)
                : PublishOutcome::failure('Could not recover the article content to publish');
        } catch (Exception $e) {
            $routeArticle->recordPublishFailed($e->getMessage());

            ActionPerformed::dispatch('Failed to publish article', LogLevelEnum::ERROR, [
                'article_id' => $article->id,
                'error' => $e->getMessage(),
            ]);

            ActivityLogged::dispatch(
                ActivityTypeEnum::ERROR,
                "Failed to publish \"{$article->title}\"",
                ['error' => $e->getMessage()],
                $article,
            );

            $this->notificationService->send(
                NotificationTypeEnum::PUBLISH_FAILED,
                NotificationSeverityEnum::ERROR,
                "Publish failed: {$article->title}",
                $e->getMessage(),
                $article,
            );

            throw $e;
        }

        match (true) {
            $outcome->succeeded() => $this->recordPublished($routeArticle),
            $outcome->wasSkipped() => $this->recordSkipped($routeArticle, $outcome),
            default => $this->recordFailed($routeArticle, $outcome),
        };

        return $outcome;
    }

    /**
     * @param  array<string, mixed>  $extractedData
     */
    private function hasPublishableContent(array $extractedData): bool
    {
        return ! empty($extractedData['description']);
    }

    /**
     * @return array<string, mixed>
     */
    private function resolvePublishData(Article $article): array
    {
        if (empty($article->description) && empty($article->image_url)) {
            return $this->fetchArticleData->execute($article);
        }

        return [
            'title' => $article->title,
            'description' => $article->description,
            'thumbnail' => $article->image_url,
        ];
    }

    private function recordPublished(RouteArticle $routeArticle): void
    {
        $routeArticle->update([
            'publish_status' => PublishStatusEnum::PUBLISHED,
            'publish_error' => null,
        ]);

        ActionPerformed::dispatch('Published article', LogLevelEnum::INFO, [
            'article_id' => $routeArticle->article->id,
            'title' => $routeArticle->article->title,
        ]);

        ActivityLogged::dispatch(
            ActivityTypeEnum::PUBLISH,
            "Published \"{$routeArticle->article->title}\"",
            ['route_article_id' => $routeArticle->id],
            $routeArticle->article,
        );
    }

    private function recordSkipped(RouteArticle $routeArticle, PublishOutcome $outcome): void
    {
        $routeArticle->update(['publish_status' => PublishStatusEnum::SKIPPED]);

        ActionPerformed::dispatch('Skipped publishing article', LogLevelEnum::INFO, [
            'article_id' => $routeArticle->article->id,
            'title' => $routeArticle->article->title,
            'reason' => $outcome->reason,
        ]);

        ActivityLogged::dispatch(
            ActivityTypeEnum::PUBLISH,
            "Skipped \"{$routeArticle->article->title}\"",
            ['skipped' => true, 'reason' => $outcome->reason],
            $routeArticle->article,
        );
    }

    private function recordFailed(RouteArticle $routeArticle, PublishOutcome $outcome): void
    {
        $article = $routeArticle->article;

        $routeArticle->recordPublishFailed($outcome->reason ?? 'Publishing failed');

        ActionPerformed::dispatch('No publication created for article', LogLevelEnum::WARNING, [
            'article_id' => $article->id,
            'title' => $article->title,
            'reason' => $outcome->reason,
        ]);

        ActivityLogged::dispatch(
            ActivityTypeEnum::ERROR,
            "Failed to publish \"{$article->title}\"",
            ['reason' => $outcome->reason],
            $article,
        );

        $this->notificationService->send(
            NotificationTypeEnum::PUBLISH_FAILED,
            NotificationSeverityEnum::WARNING,
            "Publish failed: {$article->title}",
            $outcome->reason ?? 'No publication was created for this article.',
            $article,
        );
    }
}
