<?php

declare(strict_types=1);

namespace Database\Factories;

use App\Enums\ListingStatus;
use App\Enums\ListingType;
use App\Models\Domain;
use App\Models\DomainCategory;
use App\Models\Listing;
use App\Models\SellerProfile;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Carbon;

/**
 * @extends Factory<Listing>
 */
class ListingFactory extends Factory
{
    protected $model = Listing::class;

    /** @return array<string, mixed> */
    public function definition(): array
    {
        /*
         * A listing needs a verified domain and a seller profile that belong to
         * the SAME account -- an owner mismatch would be an authorization bug
         * that every test built on this factory would then be blind to.
         */
        $domain = Domain::factory()->verified()->create();
        $seller = SellerProfile::factory()->create(['user_id' => $domain->user_id]);

        return [
            'domain_id' => $domain->getKey(),
            'user_id' => $domain->user_id,
            'seller_profile_id' => $seller->getKey(),
            'domain_category_id' => null,
            'type' => ListingType::FixedPrice,
            'status' => ListingStatus::Draft,
            'asking_price_minor' => fake()->numberBetween(50, 25_000) * 100,
            'currency' => 'USD',

            // Explicit nullables, so a created model matches a retrieved one.
            'title' => null,
            'description' => null,
            'min_offer_minor' => null,
            'featured_until' => null,
            'published_at' => null,
            'expires_at' => null,
            'sold_at' => null,
            'suspended_at' => null,
            'suspension_reason' => null,
            'metadata' => null,
            'deleted_at' => null,
        ];
    }

    /** Live and publicly visible. What a browse-surface test needs. */
    public function active(): static
    {
        return $this->state(fn (): array => [
            'status' => ListingStatus::Active,
            'published_at' => Carbon::now()->subDays(fake()->numberBetween(0, 60)),
        ]);
    }

    /**
     * Published, but deliberately carrying no price.
     *
     * The case most likely to break a price filter or a currency formatter,
     * because `asking_price_minor` is genuinely null rather than zero.
     */
    public function makeOffer(): static
    {
        return $this->state(fn (): array => [
            'type' => ListingType::MakeOffer,
            'asking_price_minor' => null,
            'status' => ListingStatus::Active,
            'published_at' => Carbon::now()->subDays(fake()->numberBetween(0, 60)),
        ]);
    }

    public function underOffer(): static
    {
        return $this->state(fn (): array => [
            'status' => ListingStatus::UnderOffer,
            'published_at' => Carbon::now()->subDays(fake()->numberBetween(1, 60)),
            'offers_count' => fake()->numberBetween(1, 5),
        ]);
    }

    public function sold(): static
    {
        return $this->state(fn (): array => [
            'status' => ListingStatus::Sold,
            'published_at' => Carbon::now()->subDays(30),
            'sold_at' => Carbon::now()->subDays(2),
        ]);
    }

    public function withdrawn(): static
    {
        return $this->state(fn (): array => [
            'status' => ListingStatus::Withdrawn,
            'published_at' => Carbon::now()->subDays(20),
        ]);
    }

    /** A staff suspension, which must never appear on a public surface. */
    public function suspended(): static
    {
        return $this->state(fn (): array => [
            'status' => ListingStatus::Suspended,
            'published_at' => Carbon::now()->subDays(10),
            'suspended_at' => Carbon::now()->subDay(),
            'suspension_reason' => 'Ownership claim disputed.',
        ]);
    }

    public function featured(): static
    {
        return $this->state(fn (): array => [
            'is_featured' => true,
            'featured_until' => Carbon::now()->addDays(30),
            'status' => ListingStatus::Active,
            'published_at' => Carbon::now()->subDays(fake()->numberBetween(0, 10)),
        ]);
    }

    /** Featured placement that has lapsed, which must not be honoured. */
    public function featureExpired(): static
    {
        return $this->state(fn (): array => [
            'is_featured' => true,
            'featured_until' => Carbon::now()->subDay(),
        ]);
    }

    public function pricedAt(int $minor): static
    {
        return $this->state(fn (): array => ['asking_price_minor' => $minor]);
    }

    /**
     * Attaches an existing domain, carrying its owner across so the listing and
     * the inventory it sells cannot disagree about who owns them.
     */
    public function forDomain(Domain $domain): static
    {
        return $this->state(function () use ($domain): array {
            $seller = SellerProfile::query()->where('user_id', $domain->user_id)->first()
                ?? SellerProfile::factory()->create(['user_id' => $domain->user_id]);

            return [
                'domain_id' => $domain->getKey(),
                'user_id' => $domain->user_id,
                'seller_profile_id' => $seller->getKey(),
            ];
        });
    }

    public function inCategory(DomainCategory $category): static
    {
        return $this->state(fn (): array => ['domain_category_id' => $category->getKey()]);
    }
}
