<?php

namespace App\Services\Operation;

use App\Models\DayTour\DayTourDate;
use App\Models\InternationalTour\InternationalTourDate;
use App\Models\Sale\Sale;
use App\Models\Sale\SaleItem;
use App\Models\Tour\TourDate;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;

/**
 * Satış kaleminde elle girilen tarihi gerçek bir "kişiye özel kalkışa" çevirir.
 *
 * Neden: operasyon modülü satışları `sales.tour_date_type/tour_date_id` üzerinden
 * kalkışlara göre gruplar (bkz. OperationController::collectDepartures). Kataloğa
 * kayıtlı kalkışı olmayan kişiye özel satışlar bu yüzden operasyonda hiç
 * görünmüyordu. Kalem tarihi girilince ilgili tur için bir kalkış üretilir;
 * böylece yolcu listesi, kaynak ataması, garanti/yola çıktı akışları — hepsi
 * hiçbir değişiklik gerekmeden çalışır.
 *
 * Bu kalkışlar `is_private = true` ve `is_published/is_bookable = false` olarak
 * açılır: siteye ve satış kataloğuna çıkmazlar.
 */
class PrivateDepartureService
{
    public function __construct(
        private DepartureInventoryService $inventory,
        private OperationService $operations,
    ) {
    }

    /**
     * Satışın kalemlerine göre özel kalkışları eşitler.
     *
     * Bir satış tek bir kalkışa bağlanabildiği için (sales.tour_date_id) tek bir
     * kalkış üretilir. Aynı tur + aynı tarih penceresine sahip kalemler
     * (yetişkin/çocuk/bebek kırılımı) bu kalkışta birleşir; farklı tur ya da
     * farklı tarihli tur kalemleri işlenemez ve `skipped` olarak raporlanır.
     * Tur olmayan tarihli kalemler (transfer, konaklama…) takvimde kalem olarak
     * görünür — bkz. AgencyCalendarController::saleItemEvents.
     *
     * @return array{departure: ?Model, skipped: int} skipped = operasyona işlenemeyen tarihli tur kalemi sayısı
     */
    public function syncForSale(Sale $sale): array
    {
        $sale->loadMissing('items');

        $primary = null;
        $primaryKey = null;
        $skipped = 0;

        foreach ($sale->items as $item) {
            $dateClass = $this->dateClassForItem($item);

            // Tarih yok ya da tur ürünü değil → varsa eski kalkışı bırak
            if (!$dateClass || !$item->start_date) {
                $this->detach($item);
                continue;
            }

            $key = $this->departureKey($item, $dateClass);

            if ($primary === null) {
                $primary = $this->upsert($sale, $item, $dateClass);
                $primaryKey = $key;
                continue;
            }

            // Aynı tur + aynı tarih (ör. yetişkin/çocuk/bebek kırılımı) tek kalkıştır;
            // ayrı bir kalkış açılmaz, kalemler aynı kalkışa bağlanır.
            if ($key === $primaryKey && $primary) {
                $this->attach($item, $primary);
                continue;
            }

            // Farklı tur/tarih → bir satış tek kalkışa bağlanabildiği için işlenemez
            $skipped++;
            $this->detach($item);
        }

        return ['departure' => $primary, 'skipped' => $skipped];
    }

    /** Aynı kalkışa ait kalemleri eşleştiren anahtar: tur + tarih penceresi. */
    private function departureKey(SaleItem $item, string $dateClass): string
    {
        return implode('|', [
            $dateClass,
            (string) $item->product?->productable_id,
            (string) $item->start_date?->toDateString(),
            (string) $item->end_date?->toDateString(),
            (string) $item->timeStr('start_time'),
            (string) $item->timeStr('end_time'),
        ]);
    }

    /** Kalemi mevcut bir kalkışa bağlar (yeni kalkış açmadan). */
    private function attach(SaleItem $item, Model $departure): void
    {
        $class = get_class($departure);
        if ($item->departure_type === $class && (int) $item->departure_id === (int) $departure->id) {
            return;
        }

        $this->detach($item);
        $item->forceFill([
            'departure_type' => $class,
            'departure_id' => $departure->id,
        ])->save();
    }

    /** Kalemin ürününden kalkış tarihi modelini çözer (tur değilse null). */
    public function dateClassForItem(SaleItem $item): ?string
    {
        $item->loadMissing('product');
        $productableType = $item->product?->productable_type;

        return $productableType
            ? $this->inventory->dateClassForProductable($productableType)
            : null;
    }

    /** Kalemin ürettiği kalkışı oluşturur ya da günceller. */
    private function upsert(Sale $sale, SaleItem $item, string $dateClass): ?Model
    {
        $tourId = $item->product?->productable_id;
        if (!$tourId) {
            return null;
        }

        // Satırın ürünü başka bir tur tipine geçtiyse (ör. günübirlik → kültür)
        // eski kalkış artık geçersizdir; öksüz kalmasın diye önce temizlenir.
        if ($item->departure_type && $item->departure_type !== $dateClass) {
            $this->detach($item);
        }

        $departure = null;
        if ($item->departure_type === $dateClass && $item->departure_id) {
            $departure = $dateClass::find($item->departure_id);
        }

        $start = Carbon::parse($item->start_date);
        $end = $item->end_date ? Carbon::parse($item->end_date) : $start->copy();
        if ($end->lt($start)) {
            $end = $start->copy();
        }

        $pax = max(1, (int) $sale->adult_count + (int) $sale->child_count + (int) $sale->infant_count);
        $label = 'Kişiye özel satış' . ($sale->sale_code ? ' #' . $sale->sale_code : '');

        // Satış iptal/iade edildiyse kalkış pasifleşir: operasyon zaten bu
        // satışları saymıyor, takvimde de görünmemeli.
        $live = !in_array($sale->status, ['cancelled', 'refunded'], true);

        $payload = match ($dateClass) {
            TourDate::class, InternationalTourDate::class => [
                ($dateClass === TourDate::class ? 'cultural_tour_id' : 'international_tour_id') => $tourId,
                'start_date' => $start->toDateString(),
                'end_date' => $end->toDateString(),
                'start_time' => $item->timeStr('start_time'),
                'end_time' => $item->timeStr('end_time'),
                'total_capacity' => $pax,
                'adult_capacity' => $pax,
                'currency' => $item->currency ?: ($sale->currency ?: 'TRY'),
                'status' => $live ? 'confirmed' : 'cancelled',
                'is_active' => $live,
                'is_published' => false,
                'is_bookable' => false,
                'guarantee_departure' => true,
                'special_notes' => $label,
                'is_private' => true,
                'private_sale_id' => $sale->id,
            ],
            DayTourDate::class => [
                'day_tour_id' => $tourId,
                'tour_date' => $start->toDateString(),
                'start_time' => $item->timeStr('start_time'),
                'end_time' => $item->timeStr('end_time'),
                'status' => $live ? 'guaranteed' : 'cancelled',
                'is_active' => $live,
                'is_guaranteed' => true,
                'max_participants' => $pax,
                'notes' => $label,
                'is_private' => true,
                'private_sale_id' => $sale->id,
            ],
            default => null,
        };

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

        if ($departure) {
            $departure->update($payload);
        } else {
            $departure = $dateClass::create($payload);
            $item->forceFill([
                'departure_type' => $dateClass,
                'departure_id' => $departure->id,
            ])->save();
        }

        return $departure;
    }

    /**
     * Satıştan çıkarılacak kalemlerin ürettiği özel kalkışları temizler.
     * Kalemler silinmeden ÖNCE çağrılmalıdır; aksi halde kalkış öksüz kalır.
     *
     * @param iterable<SaleItem> $items
     */
    public function detachItems(iterable $items): void
    {
        foreach ($items as $item) {
            $this->detach($item);
        }
    }

    /** Kalemin ürettiği kalkış artık geçersiz → kalkışı sil, bağı kopar. */
    private function detach(SaleItem $item): void
    {
        if (!$item->departure_type || !$item->departure_id) {
            return;
        }

        $class = $item->departure_type;

        // Aynı kalkışa bağlı başka kalem varsa (yetişkin/çocuk/bebek kırılımı)
        // kalkış silinmez — yalnızca bu kalemin bağı kopar.
        $sharedWithOthers = SaleItem::where('departure_type', $class)
            ->where('departure_id', $item->departure_id)
            ->where('id', '!=', $item->id)
            ->exists();

        if (!$sharedWithOthers && class_exists($class) && ($departure = $class::find($item->departure_id))) {
            // Yalnızca bu satış için açılmış özel kalkışlar silinir; kataloğa
            // kayıtlı gerçek bir kalkış asla silinmez.
            if ($departure->is_private) {
                $departure->delete();
            }
        }

        $item->forceFill(['departure_type' => null, 'departure_id' => null])->save();
    }

    /**
     * Satışı özel kalkışa bağlar ve envanter/operasyon durumunu tazeler.
     * `syncDeparture` ile aynı işi yapar; farkı, tarih modelini satışın
     * reservable'ından değil doğrudan kalkışın kendisinden almasıdır.
     */
    public function bindSale(Sale $sale, Model $departure): void
    {
        $oldType = $sale->getOriginal('tour_date_type');
        $oldId = $sale->getOriginal('tour_date_id');

        $sale->tour_date_type = get_class($departure);
        $sale->tour_date_id = $departure->id;
        $sale->save();

        $this->inventory->recount($departure);
        $this->operations->evaluate($departure);

        // Önceki kalkış başkasıysa onun da sayaçları tazelenmeli
        if ($oldType && $oldId && ($oldType !== $sale->tour_date_type || (int) $oldId !== (int) $sale->tour_date_id)) {
            if ($old = $this->inventory->resolve($oldType, (int) $oldId)) {
                $this->inventory->recount($old);
                $this->operations->evaluate($old);
            }
        }
    }
}
