<?php

declare(strict_types=1);

namespace App\Domain\PreSave;

use App\Domain\PreSave\ValueObjects\CampaignStatus;
use App\Domain\PreSave\ValueObjects\StreamingPlatform;
use App\Domain\PreSave\ValueObjects\TrackUrl;
use App\Framework\Core\ValueObjects\Timestamp;

/**
 * Pre-Save Campaign Entity
 *
 * Represents a music pre-save campaign for upcoming releases
 */
final readonly class PreSaveCampaign
{
    /**
     * @param array<TrackUrl> $trackUrls
     */
    public function __construct(
        public ?int $id,
        public string $title,
        public string $artistName,
        public string $coverImageUrl,
        public Timestamp $releaseDate,
        public array $trackUrls,
        public CampaignStatus $status,
        public Timestamp $createdAt,
        public Timestamp $updatedAt,
        public ?string $description = null,
        public ?Timestamp $startDate = null,
    ) {
        if (empty($this->trackUrls)) {
            throw new \InvalidArgumentException('Campaign must have at least one track URL');
        }
    }

    /**
     * Create new campaign
     *
     * @param array<TrackUrl> $trackUrls
     */
    public static function create(
        string $title,
        string $artistName,
        string $coverImageUrl,
        Timestamp $releaseDate,
        array $trackUrls,
        ?string $description = null,
        ?Timestamp $startDate = null,
    ): self {
        $now = Timestamp::now();

        return new self(
            id: null,
            title: $title,
            artistName: $artistName,
            coverImageUrl: $coverImageUrl,
            releaseDate: $releaseDate,
            trackUrls: $trackUrls,
            status: CampaignStatus::DRAFT,
            createdAt: $now,
            updatedAt: $now,
            description: $description,
            startDate: $startDate,
        );
    }

    /**
     * Publish campaign (make it active)
     */
    public function publish(): self
    {
        if (! $this->status->isEditable()) {
            throw new \RuntimeException('Cannot publish campaign in current status: ' . $this->status->value);
        }

        $now = Timestamp::now();
        $newStatus = $now->isBefore($this->releaseDate) ? CampaignStatus::SCHEDULED : CampaignStatus::ACTIVE;

        return new self(
            id: $this->id,
            title: $this->title,
            artistName: $this->artistName,
            coverImageUrl: $this->coverImageUrl,
            releaseDate: $this->releaseDate,
            trackUrls: $this->trackUrls,
            status: $newStatus,
            createdAt: $this->createdAt,
            updatedAt: Timestamp::now(),
            description: $this->description,
            startDate: $this->startDate ?? $now,
        );
    }

    /**
     * Mark as released (ready for processing)
     */
    public function markAsReleased(): self
    {
        return new self(
            id: $this->id,
            title: $this->title,
            artistName: $this->artistName,
            coverImageUrl: $this->coverImageUrl,
            releaseDate: $this->releaseDate,
            trackUrls: $this->trackUrls,
            status: CampaignStatus::RELEASED,
            createdAt: $this->createdAt,
            updatedAt: Timestamp::now(),
            description: $this->description,
            startDate: $this->startDate,
        );
    }

    /**
     * Mark as completed
     */
    public function markAsCompleted(): self
    {
        return new self(
            id: $this->id,
            title: $this->title,
            artistName: $this->artistName,
            coverImageUrl: $this->coverImageUrl,
            releaseDate: $this->releaseDate,
            trackUrls: $this->trackUrls,
            status: CampaignStatus::COMPLETED,
            createdAt: $this->createdAt,
            updatedAt: Timestamp::now(),
            description: $this->description,
            startDate: $this->startDate,
        );
    }

    /**
     * Cancel campaign
     */
    public function cancel(): self
    {
        if ($this->status === CampaignStatus::COMPLETED) {
            throw new \RuntimeException('Cannot cancel completed campaign');
        }

        return new self(
            id: $this->id,
            title: $this->title,
            artistName: $this->artistName,
            coverImageUrl: $this->coverImageUrl,
            releaseDate: $this->releaseDate,
            trackUrls: $this->trackUrls,
            status: CampaignStatus::CANCELLED,
            createdAt: $this->createdAt,
            updatedAt: Timestamp::now(),
            description: $this->description,
            startDate: $this->startDate,
        );
    }

    /**
     * Get track URL for specific platform
     */
    public function getTrackUrl(StreamingPlatform $platform): ?TrackUrl
    {
        foreach ($this->trackUrls as $trackUrl) {
            if ($trackUrl->platform === $platform) {
                return $trackUrl;
            }
        }

        return null;
    }

    /**
     * Check if campaign has track for platform
     */
    public function hasPlatform(StreamingPlatform $platform): bool
    {
        return $this->getTrackUrl($platform) !== null;
    }

    /**
     * Check if campaign is currently accepting registrations
     */
    public function acceptsRegistrations(): bool
    {
        return $this->status->acceptsRegistrations();
    }

    /**
     * Check if release date has passed
     */
    public function hasReleased(): bool
    {
        return $this->releaseDate->isBefore(Timestamp::now());
    }

    /**
     * Get days until release
     */
    public function getDaysUntilRelease(): int
    {
        $now = Timestamp::now();
        $diff = $this->releaseDate->toTimestamp() - $now->toTimestamp();

        return (int) ceil($diff / 86400);
    }

    /**
     * Convert to array for storage/API
     *
     * @return array<string, mixed>
     */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'artist_name' => $this->artistName,
            'cover_image_url' => $this->coverImageUrl,
            'description' => $this->description,
            'release_date' => $this->releaseDate->toTimestamp(),
            'start_date' => $this->startDate?->toTimestamp(),
            'track_urls' => array_map(fn ($url) => $url->toArray(), $this->trackUrls),
            'status' => $this->status->value,
            'created_at' => $this->createdAt->toDateTime()->format('Y-m-d H:i:s'),
            'updated_at' => $this->updatedAt->toDateTime()->format('Y-m-d H:i:s'),
        ];
    }

    /**
     * Create from database row
     *
     * @param array<string, mixed> $row
     */
    public static function fromArray(array $row): self
    {
        $trackUrls = is_string($row['track_urls'])
            ? json_decode($row['track_urls'], true)
            : $row['track_urls'];

        return new self(
            id: isset($row['id']) ? (int) $row['id'] : null,
            title: (string) $row['title'],
            artistName: (string) $row['artist_name'],
            coverImageUrl: (string) $row['cover_image_url'],
            releaseDate: Timestamp::fromFloat((float) $row['release_date']),
            trackUrls: array_map(
                fn (array $data) => TrackUrl::fromArray($data),
                $trackUrls
            ),
            status: CampaignStatus::from($row['status']),
            createdAt: Timestamp::fromFloat((float) strtotime($row['created_at'])),
            updatedAt: Timestamp::fromFloat((float) strtotime($row['updated_at'])),
            description: $row['description'] ?? null,
            startDate: isset($row['start_date']) ? Timestamp::fromFloat((float) $row['start_date']) : null,
        );
    }
}
