<?php

declare(strict_types=1);

namespace App\Models;

use App\Models\CollectionComment;
use App\Models\CollectionDailyStat;
use App\Models\CollectionHistory;
use App\Models\CollectionMember;
use App\Models\CollectionSave;
use App\Models\CollectionSurfacePlacement;
use App\Models\CollectionSubmission;
use App\Models\Group;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;

class Collection extends Model
{
    use HasFactory, SoftDeletes;

    public const LIFECYCLE_DRAFT = 'draft';
    public const LIFECYCLE_SCHEDULED = 'scheduled';
    public const LIFECYCLE_PUBLISHED = 'published';
    public const LIFECYCLE_FEATURED = 'featured';
    public const LIFECYCLE_ARCHIVED = 'archived';
    public const LIFECYCLE_HIDDEN = 'hidden';
    public const LIFECYCLE_RESTRICTED = 'restricted';
    public const LIFECYCLE_UNDER_REVIEW = 'under_review';
    public const LIFECYCLE_EXPIRED = 'expired';

    public const WORKFLOW_DRAFT = 'draft';
    public const WORKFLOW_IN_REVIEW = 'in_review';
    public const WORKFLOW_APPROVED = 'approved';
    public const WORKFLOW_PROGRAMMED = 'programmed';
    public const WORKFLOW_ARCHIVED = 'archived';

    public const READINESS_READY = 'ready';
    public const READINESS_NEEDS_WORK = 'needs_work';
    public const READINESS_BLOCKED = 'blocked';

    public const HEALTH_HEALTHY = 'healthy';
    public const HEALTH_NEEDS_METADATA = 'needs_metadata';
    public const HEALTH_STALE = 'stale';
    public const HEALTH_LOW_CONTENT = 'low_content';
    public const HEALTH_BROKEN_ITEMS = 'broken_items';
    public const HEALTH_WEAK_COVER = 'weak_cover';
    public const HEALTH_LOW_ENGAGEMENT = 'low_engagement';
    public const HEALTH_ATTRIBUTION_INCOMPLETE = 'attribution_incomplete';
    public const HEALTH_NEEDS_REVIEW = 'needs_review';
    public const HEALTH_DUPLICATE_RISK = 'duplicate_risk';
    public const HEALTH_MERGE_CANDIDATE = 'merge_candidate';

    public const TYPE_PERSONAL = 'personal';
    public const TYPE_COMMUNITY = 'community';
    public const TYPE_EDITORIAL = 'editorial';

    public const EDITORIAL_OWNER_CREATOR = 'creator';
    public const EDITORIAL_OWNER_STAFF_ACCOUNT = 'staff_account';
    public const EDITORIAL_OWNER_SYSTEM = 'system';

    public const COLLABORATION_CLOSED = 'closed';
    public const COLLABORATION_INVITE_ONLY = 'invite_only';
    public const COLLABORATION_OPEN = 'open';

    public const MODERATION_ACTIVE = 'active';
    public const MODERATION_UNDER_REVIEW = 'under_review';
    public const MODERATION_REVIEW = self::MODERATION_UNDER_REVIEW;
    public const MODERATION_RESTRICTED = 'restricted';
    public const MODERATION_HIDDEN = 'hidden';

    public const MEMBER_ROLE_OWNER = 'owner';
    public const MEMBER_ROLE_EDITOR = 'editor';
    public const MEMBER_ROLE_CONTRIBUTOR = 'contributor';
    public const MEMBER_ROLE_VIEWER = 'viewer';

    public const MEMBER_STATUS_PENDING = 'pending';
    public const MEMBER_STATUS_ACTIVE = 'active';
    public const MEMBER_STATUS_REVOKED = 'revoked';

    public const SUBMISSION_PENDING = 'pending';
    public const SUBMISSION_APPROVED = 'approved';
    public const SUBMISSION_REJECTED = 'rejected';
    public const SUBMISSION_WITHDRAWN = 'withdrawn';

    public const COMMENT_VISIBLE = 'visible';
    public const COMMENT_HIDDEN = 'hidden';
    public const COMMENT_FLAGGED = 'flagged';

    public const VISIBILITY_PUBLIC = 'public';
    public const VISIBILITY_UNLISTED = 'unlisted';
    public const VISIBILITY_PRIVATE = 'private';

    public const MODE_MANUAL = 'manual';
    public const MODE_SMART = 'smart';

    public const SORT_MANUAL = 'manual';
    public const SORT_NEWEST = 'newest';
    public const SORT_OLDEST = 'oldest';
    public const SORT_POPULAR = 'popular';

    public const SPOTLIGHT_STYLE_DEFAULT = 'default';
    public const SPOTLIGHT_STYLE_EDITORIAL = 'editorial';
    public const SPOTLIGHT_STYLE_SEASONAL = 'seasonal';
    public const SPOTLIGHT_STYLE_CHALLENGE = 'challenge';
    public const SPOTLIGHT_STYLE_COMMUNITY = 'community';

    public const PRESENTATION_STANDARD = 'standard';
    public const PRESENTATION_EDITORIAL_GRID = 'editorial_grid';
    public const PRESENTATION_HERO_GRID = 'hero_grid';
    public const PRESENTATION_MASONRY = 'masonry';

    public const EMPHASIS_COVER_HEAVY = 'cover_heavy';
    public const EMPHASIS_BALANCED = 'balanced';
    public const EMPHASIS_ARTWORK_FIRST = 'artwork_first';

    protected $fillable = [
        'user_id',
        'group_id',
        'managed_by_user_id',
        'title',
        'slug',
        'lifecycle_state',
        'workflow_state',
        'readiness_state',
        'health_state',
        'health_flags_json',
        'canonical_collection_id',
        'duplicate_cluster_key',
        'program_key',
        'partner_key',
        'trust_tier',
        'experiment_key',
        'experiment_treatment',
        'placement_variant',
        'ranking_mode_variant',
        'collection_pool_version',
        'test_label',
        'recommendation_tier',
        'ranking_bucket',
        'search_boost_tier',
        'type',
        'editorial_owner_mode',
        'editorial_owner_user_id',
        'editorial_owner_label',
        'description',
        'subtitle',
        'summary',
        'collaboration_mode',
        'allow_submissions',
        'allow_comments',
        'allow_saves',
        'moderation_status',
        'published_at',
        'unpublished_at',
        'event_key',
        'event_label',
        'season_key',
        'banner_text',
        'badge_label',
        'spotlight_style',
        'quality_score',
        'ranking_score',
        'metadata_completeness_score',
        'editorial_readiness_score',
        'freshness_score',
        'engagement_score',
        'health_score',
        'last_health_check_at',
        'last_recommendation_refresh_at',
        'placement_eligibility',
        'analytics_enabled',
        'presentation_style',
        'emphasis_mode',
        'theme_token',
        'series_key',
        'series_title',
        'series_description',
        'series_order',
        'campaign_key',
        'campaign_label',
        'commercial_eligibility',
        'promotion_tier',
        'sponsorship_label',
        'sponsorship_state',
        'partner_label',
        'ownership_domain',
        'commercial_review_state',
        'legal_review_state',
        'monetization_ready_status',
        'brand_safe_status',
        'editorial_notes',
        'staff_commercial_notes',
        'archived_at',
        'expired_at',
        'history_count',
        'cover_artwork_id',
        'visibility',
        'mode',
        'sort_mode',
        'artworks_count',
        'comments_count',
        'saves_count',
        'collaborators_count',
        'is_featured',
        'profile_order',
        'views_count',
        'likes_count',
        'followers_count',
        'shares_count',
        'smart_rules_json',
        'layout_modules_json',
        'last_activity_at',
        'featured_at',
    ];

    protected $casts = [
        'artworks_count' => 'integer',
        'comments_count' => 'integer',
        'saves_count' => 'integer',
        'collaborators_count' => 'integer',
        'is_featured' => 'boolean',
        'profile_order' => 'integer',
        'views_count' => 'integer',
        'likes_count' => 'integer',
        'followers_count' => 'integer',
        'shares_count' => 'integer',
        'allow_submissions' => 'boolean',
        'allow_comments' => 'boolean',
        'allow_saves' => 'boolean',
        'analytics_enabled' => 'boolean',
        'commercial_eligibility' => 'boolean',
        'smart_rules_json' => 'array',
        'layout_modules_json' => 'array',
        'health_flags_json' => 'array',
        'quality_score' => 'decimal:2',
        'ranking_score' => 'decimal:2',
        'metadata_completeness_score' => 'decimal:2',
        'editorial_readiness_score' => 'decimal:2',
        'freshness_score' => 'decimal:2',
        'engagement_score' => 'decimal:2',
        'health_score' => 'decimal:2',
        'placement_eligibility' => 'boolean',
        'series_order' => 'integer',
        'history_count' => 'integer',
        'last_activity_at' => 'datetime',
        'featured_at' => 'datetime',
        'last_health_check_at' => 'datetime',
        'last_recommendation_refresh_at' => 'datetime',
        'published_at' => 'datetime',
        'unpublished_at' => 'datetime',
        'archived_at' => 'datetime',
        'expired_at' => 'datetime',
    ];

    protected ?bool $cachedVisibilityWindow = null;

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function group(): BelongsTo
    {
        return $this->belongsTo(Group::class);
    }

    public function managedBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'managed_by_user_id');
    }

    public function editorialOwnerUser(): BelongsTo
    {
        return $this->belongsTo(User::class, 'editorial_owner_user_id');
    }

    public function coverArtwork(): BelongsTo
    {
        return $this->belongsTo(Artwork::class, 'cover_artwork_id');
    }

    public function likes(): HasMany
    {
        return $this->hasMany(CollectionLike::class);
    }

    public function saves(): HasMany
    {
        return $this->hasMany(CollectionSave::class);
    }

    public function follows(): HasMany
    {
        return $this->hasMany(CollectionFollow::class);
    }

    public function members(): HasMany
    {
        return $this->hasMany(CollectionMember::class);
    }

    public function submissions(): HasMany
    {
        return $this->hasMany(CollectionSubmission::class);
    }

    public function comments(): HasMany
    {
        return $this->hasMany(CollectionComment::class);
    }

    public function historyEntries(): HasMany
    {
        return $this->hasMany(CollectionHistory::class);
    }

    public function canonicalCollection(): BelongsTo
    {
        return $this->belongsTo(self::class, 'canonical_collection_id');
    }

    public function dailyStats(): HasMany
    {
        return $this->hasMany(CollectionDailyStat::class);
    }

    public function programAssignments(): HasMany
    {
        return $this->hasMany(CollectionProgramAssignment::class);
    }

    public function qualitySnapshots(): HasMany
    {
        return $this->hasMany(CollectionQualitySnapshot::class);
    }

    public function recommendationSnapshots(): HasMany
    {
        return $this->hasMany(CollectionRecommendationSnapshot::class);
    }

    public function mergeActionsAsSource(): HasMany
    {
        return $this->hasMany(CollectionMergeAction::class, 'source_collection_id');
    }

    public function mergeActionsAsTarget(): HasMany
    {
        return $this->hasMany(CollectionMergeAction::class, 'target_collection_id');
    }

    public function entityLinks(): HasMany
    {
        return $this->hasMany(CollectionEntityLink::class);
    }

    public function savedNotes(): HasMany
    {
        return $this->hasMany(CollectionSavedNote::class);
    }

    public function placements(): HasMany
    {
        return $this->hasMany(CollectionSurfacePlacement::class);
    }

    public function artworks(): BelongsToMany
    {
        return $this->belongsToMany(Artwork::class, 'collection_artwork', 'collection_id', 'artwork_id')
            ->withPivot(['order_num'])
            ->withTimestamps()
            ->orderByPivot('order_num');
    }

    public function publicArtworks(): BelongsToMany
    {
        return $this->artworks()
            ->whereNull('artworks.deleted_at')
            ->where('artworks.is_public', true)
            ->where('artworks.is_approved', true)
            ->whereNotNull('artworks.published_at')
            ->where('artworks.published_at', '<=', now());
    }

    public function manualRelatedCollections(): BelongsToMany
    {
        return $this->belongsToMany(self::class, 'collection_related_links', 'collection_id', 'related_collection_id')
            ->withPivot(['sort_order', 'created_by_user_id'])
            ->withTimestamps()
            ->orderBy('collection_related_links.sort_order')
            ->orderBy('collection_related_links.id');
    }

    public function scopePublic(Builder $query): Builder
    {
        return $query
            ->where('visibility', self::VISIBILITY_PUBLIC)
            ->where('moderation_status', self::MODERATION_ACTIVE)
            ->whereNotIn('lifecycle_state', [
                self::LIFECYCLE_DRAFT,
                self::LIFECYCLE_EXPIRED,
                self::LIFECYCLE_HIDDEN,
                self::LIFECYCLE_RESTRICTED,
                self::LIFECYCLE_UNDER_REVIEW,
            ])
            ->where(function (Builder $builder): void {
                $builder->whereNull('published_at')
                    ->orWhere('published_at', '<=', now());
            })
            ->where(function (Builder $builder): void {
                $builder->whereNull('unpublished_at')
                    ->orWhere('unpublished_at', '>', now());
            })
            ->where(function (Builder $builder): void {
                $builder->whereNull('expired_at')
                    ->orWhere('expired_at', '>', now());
            });
    }

    public function scopePublicEligible(Builder $query): Builder
    {
        return $query
            ->public()
            ->where('placement_eligibility', true)
            ->whereIn('lifecycle_state', [
                self::LIFECYCLE_PUBLISHED,
                self::LIFECYCLE_FEATURED,
            ]);
    }

    public function scopeFeaturedPublic(Builder $query): Builder
    {
        return $query
            ->publicEligible()
            ->where('is_featured', true);
    }

    public function scopeVisibleOnProfile(Builder $query): Builder
    {
        return $query->public();
    }

    public function scopeOwnedBy(Builder $query, int $userId): Builder
    {
        return $query->where('user_id', $userId);
    }

    public function isOwnedBy(User|int|null $user): bool
    {
        $userId = $user instanceof User ? $user->id : $user;

        if ($userId === null) {
            return false;
        }

        if ((int) ($this->group_id ?? 0) > 0) {
            $group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();

            return $group?->hasActiveMember((int) $userId) ?? false;
        }

        return (int) $userId === (int) $this->user_id;
    }

    public function isPubliclyAccessible(): bool
    {
        if ($this->moderation_status !== self::MODERATION_ACTIVE) {
            return false;
        }

        if ($this->published_at && $this->published_at->isFuture()) {
            return false;
        }

        if ($this->unpublished_at && $this->unpublished_at->lte(now())) {
            return false;
        }

        if ($this->expired_at && $this->expired_at->lte(now()) && $this->lifecycle_state === self::LIFECYCLE_EXPIRED) {
            return false;
        }

        if (! in_array($this->lifecycle_state, [
            self::LIFECYCLE_SCHEDULED,
            self::LIFECYCLE_PUBLISHED,
            self::LIFECYCLE_FEATURED,
            self::LIFECYCLE_ARCHIVED,
        ], true)) {
            return false;
        }

        return in_array($this->visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED], true);
    }

    public function isPubliclyEngageable(): bool
    {
        return $this->visibility === self::VISIBILITY_PUBLIC;
    }

    public function displayOwnerName(): string
    {
        if ((int) ($this->group_id ?? 0) > 0) {
            $group = $this->relationLoaded('group') ? $this->group : $this->group()->first();

            return (string) ($group?->name ?: 'Skinbase Group');
        }

        if ($this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM) {
            return (string) ($this->editorial_owner_label ?: config('collections.editorial.system_owner_label', 'Skinbase Editorial'));
        }

        $owner = $this->relationLoaded('user') ? $this->user : $this->user()->first();

        return (string) ($owner?->name ?: $owner?->username ?: 'Skinbase Curator');
    }

    public function displayOwnerUsername(): ?string
    {
        if ((int) ($this->group_id ?? 0) > 0) {
            return null;
        }

        if ($this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM) {
            return null;
        }

        $owner = $this->relationLoaded('user') ? $this->user : $this->user()->first();

        return $owner?->username;
    }

    public function hasSystemEditorialOwner(): bool
    {
        return $this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM;
    }

    public function isCollaborative(): bool
    {
        return $this->type !== self::TYPE_PERSONAL || $this->collaboration_mode !== self::COLLABORATION_CLOSED;
    }

    public function activeMemberRoleFor(User|int|null $user): ?string
    {
        $userId = $user instanceof User ? $user->id : $user;

        if ($userId === null) {
            return null;
        }

        if ((int) ($this->group_id ?? 0) > 0) {
            $group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();

            return $group?->activeRoleFor((int) $userId);
        }

        if ($this->isOwnedBy($userId)) {
            return self::MEMBER_ROLE_OWNER;
        }

        $members = $this->relationLoaded('members')
            ? $this->members
            : $this->members()->where('status', self::MEMBER_STATUS_ACTIVE)->get();

        return $members
            ->first(fn (CollectionMember $member) => (int) $member->user_id === (int) $userId && $member->status === self::MEMBER_STATUS_ACTIVE)
            ?->role;
    }

    public function hasActiveMember(User|int|null $user): bool
    {
        return $this->activeMemberRoleFor($user) !== null;
    }

    public function canBeManagedBy(User $user): bool
    {
        if ((int) ($this->group_id ?? 0) > 0) {
            $group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();

            return $group?->canManageCollections($user) ?? false;
        }

        return in_array($this->activeMemberRoleFor($user), [self::MEMBER_ROLE_OWNER, self::MEMBER_ROLE_EDITOR], true);
    }

    public function canManageArtworks(User $user): bool
    {
        return $this->canBeManagedBy($user);
    }

    public function canManageMembers(User $user): bool
    {
        if ((int) ($this->group_id ?? 0) > 0) {
            $group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();

            return $group?->canManageMembers($user) ?? false;
        }

        return $this->isCollaborative() && $this->canBeManagedBy($user);
    }

    public function canReceiveSubmissionsFrom(?User $user): bool
    {
        if (! $user || ! $this->allow_submissions || ! $this->isPubliclyAccessible()) {
            return false;
        }

        if ($this->type === self::TYPE_EDITORIAL) {
            return false;
        }

        if ($this->collaboration_mode === self::COLLABORATION_CLOSED) {
            return false;
        }

        return ! $this->hasActiveMember($user);
    }

    public function canReceiveCommentsFrom(?User $user): bool
    {
        return $user !== null && $this->allow_comments && $this->canBeViewedBy($user);
    }

    public function canBeSavedBy(?User $user): bool
    {
        return $user !== null && $this->allow_saves && $this->visibility === self::VISIBILITY_PUBLIC && ! $this->isOwnedBy($user);
    }

    public function isFeatureablePublicly(): bool
    {
        return $this->visibility === self::VISIBILITY_PUBLIC
            && $this->moderation_status === self::MODERATION_ACTIVE
            && (bool) $this->placement_eligibility
            && in_array($this->lifecycle_state, [self::LIFECYCLE_PUBLISHED, self::LIFECYCLE_FEATURED], true);
    }

    public function isSmart(): bool
    {
        return $this->mode === self::MODE_SMART;
    }

    public function canBeViewedBy(?User $viewer): bool
    {
        if ($viewer && ($this->isOwnedBy($viewer) || $this->hasActiveMember($viewer))) {
            return true;
        }

        return $this->isPubliclyAccessible();
    }

    public function resolvedCoverArtwork(bool $publicOnly = false, bool $hideMature = false): ?Artwork
    {
        $cover = $this->relationLoaded('coverArtwork') ? $this->coverArtwork : $this->coverArtwork()->first();
        if ($cover && $this->artworkMatchesCoverVisibility($cover, $publicOnly, $hideMature)) {
            return $cover;
        }

        $relation = $publicOnly ? 'publicArtworks' : 'artworks';
        $artworks = $this->relationLoaded($relation)
            ? $this->getRelation($relation)
            : $this->{$relation}()
                ->when($hideMature, function ($query): void {
                    $query->whereRaw('COALESCE(artworks.is_mature, 0) = 0')
                        ->whereRaw("COALESCE(artworks.maturity_status, 'clear') != ?", ['suspected']);
                })
                ->limit(1)
                ->get();

        return $artworks
            ->first(fn (Artwork $artwork): bool => $this->artworkMatchesCoverVisibility($artwork, $publicOnly, $hideMature));
    }

    public function syncArtworksCount(): void
    {
        $this->forceFill([
            'artworks_count' => $this->isSmart()
                ? (int) $this->artworks_count
                : $this->artworks()->count(),
        ])->save();
    }

    public function inSeries(): bool
    {
        return filled($this->series_key);
    }

    public function hasCanonicalTarget(): bool
    {
        return $this->canonical_collection_id !== null;
    }

    public function isPlacementEligible(): bool
    {
        return (bool) $this->placement_eligibility;
    }

    public function supportsAnalytics(): bool
    {
        return (bool) $this->analytics_enabled;
    }

    public function usesPremiumPresentation(): bool
    {
        return $this->presentation_style !== self::PRESENTATION_STANDARD
            || $this->emphasis_mode !== self::EMPHASIS_BALANCED
            || filled($this->theme_token);
    }

    private function artworkIsPubliclyVisible(Artwork $artwork): bool
    {
        return ! $artwork->trashed()
            && (bool) $artwork->is_public
            && (bool) $artwork->is_approved
            && $artwork->published_at !== null
            && $artwork->published_at->lte(now());
    }

    private function artworkMatchesCoverVisibility(Artwork $artwork, bool $publicOnly, bool $hideMature): bool
    {
        if ($publicOnly && ! $this->artworkIsPubliclyVisible($artwork)) {
            return false;
        }

        if (! $hideMature) {
            return true;
        }

        return ! (bool) $artwork->is_mature
            && (string) ($artwork->maturity_status ?? 'clear') !== 'suspected';
    }
}
