<?php

namespace App\Domain\Facility\Services;

use App\Domain\Facility\Models\Facility;
use App\Domain\Facility\Models\SpaceLayout;
use App\Domain\Facility\Models\SpaceReservation;
use App\Domain\Facility\Models\SpaceSegment;
use App\Domain\Shared\Exceptions\DomainException;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class SpaceLayoutService
{
    /**
     * Create a new space layout with auto-generated segments.
     */
    public function create(array $data, User $creator): SpaceLayout
    {
        return DB::transaction(function () use ($data, $creator) {
            $this->validateNoOverlap($data);

            // A layout subdivides one facility, so its branch is that
            // facility's — never the actor's. Letting the creating hook stamp
            // the active branch instead filed the layout (and every segment
            // generated under it) against whichever branch happened to be
            // selected, and left it null entirely on the console and listener
            // paths, where it would then be invisible from every branch.
            $facility = Facility::withoutBranchScope()->find($data['facility_id'] ?? null);

            $layout = SpaceLayout::create([
                ...$data,
                'branch_id' => $facility?->branch_id ?? ($data['branch_id'] ?? null),
                'created_by' => $creator->id,
            ]);

            $this->generateSegments($layout);

            return $layout->load('segments');
        });
    }

    /**
     * Update a layout. Regenerates segments if type/config changed.
     */
    /**
     * The starting layout for a new facility.
     *
     * Defaults to a single 1x1 cell — "the whole facility, not subdivided yet" —
     * which is honest and keeps the schedule grid from rendering empty. The
     * academy resizes it in facility settings whenever they are ready.
     */
    public function createDefaultLayout(
        Facility $facility,
        User $creator,
        int $rows = 1,
        int $columns = 1
    ): SpaceLayout {
        return $this->create([
            'academy_id'    => $facility->academy_id,
            'facility_id'   => $facility->id,
            'name'          => 'Default layout',
            'name_ar'       => 'التقسيم الافتراضي',
            'layout_type'   => 'grid',
            'layout_config' => ['rows' => max(1, $rows), 'columns' => max(1, $columns)],
            'is_recurring'  => true,
            'start_time'    => $facility->operating_start ?: '00:00',
            'end_time'      => $facility->operating_end ?: '23:59',
            'metadata'      => ['auto_created' => true],
        ], $creator);
    }

    /**
     * Resize a grid layout in place, regenerating its segments.
     *
     * Segments currently held by a confirmed reservation are protected — a
     * shrink that would orphan a booking is refused rather than silently
     * dropping someone's slot.
     */
    public function resizeGrid(SpaceLayout $layout, int $rows, int $columns): SpaceLayout
    {
        $rows = max(1, $rows);
        $columns = max(1, $columns);

        $keptCodes = [];
        for ($r = 1; $r <= $rows; $r++) {
            for ($c = 1; $c <= $columns; $c++) {
                $keptCodes[] = "R{$r}C{$c}";
            }
        }

        $droppedIds = $layout->segments()
            ->whereNotIn('code', $keptCodes)
            ->pluck('id');

        if ($droppedIds->isNotEmpty()) {
            $inUse = SpaceReservation::where('status', 'confirmed')
                ->where('facility_id', $layout->facility_id)
                ->whereDate('reservation_date', '>=', now()->toDateString())
                ->get()
                ->filter(function ($reservation) use ($droppedIds) {
                    $segments = is_array($reservation->segment_ids)
                        ? $reservation->segment_ids
                        : json_decode($reservation->segment_ids ?? '[]', true);

                    return ! empty(array_intersect($droppedIds->all(), $segments ?? []));
                });

            if ($inUse->isNotEmpty()) {
                throw new DomainException(
                    'لا يمكن تصغير التقسيم: توجد حجوزات مؤكدة على الأجزاء التي ستُحذف'
                );
            }
        }

        return $this->update($layout, [
            'layout_type'   => 'grid',
            'layout_config' => ['rows' => $rows, 'columns' => $columns],
        ]);
    }

    public function update(SpaceLayout $layout, array $data): SpaceLayout
    {
        return DB::transaction(function () use ($layout, $data) {
            $this->validateNoOverlap($data, $layout->id);

            $oldType = $layout->layout_type->value;
            $oldConfig = $layout->layout_config;

            $newType = $data['layout_type'] ?? $oldType;
            $newConfig = $data['layout_config'] ?? $oldConfig;

            $layout->update($data);

            $configChanged = $newType !== $oldType || json_encode($newConfig) !== json_encode($oldConfig);

            if ($configChanged) {
                $layout->segments()->delete();
                $this->generateSegments($layout->fresh());
            }

            return $layout->fresh(['segments']);
        });
    }

    /**
     * Delete a layout and its segments.
     */
    public function delete(SpaceLayout $layout): void
    {
        DB::transaction(function () use ($layout) {
            $layout->segments()->delete();
            $layout->delete();
        });
    }

    /**
     * Resolve which layout applies for a given facility, date, and time.
     * Specific-date layouts override recurring ones.
     */
    public function resolveLayout(int $facilityId, string $date, string $time): ?SpaceLayout
    {
        $dayOfWeek = (int) date('w', strtotime($date));

        // Specific date override takes priority
        $specificLayout = SpaceLayout::where('facility_id', $facilityId)
            ->where('is_recurring', false)
            ->where('effective_date', $date)
            ->where('start_time', '<=', $time)
            ->where('end_time', '>', $time)
            ->where('is_active', true)
            ->first();

        if ($specificLayout) {
            return $specificLayout;
        }

        // Fall back to recurring layout for this day of week
        return SpaceLayout::where('facility_id', $facilityId)
            ->where('is_recurring', true)
            ->where('effective_day_of_week', $dayOfWeek)
            ->where('start_time', '<=', $time)
            ->where('end_time', '>', $time)
            ->where('is_active', true)
            ->first();
    }

    /**
     * Get all available segments for a facility at a given date/time.
     */
    public function getAvailableSegments(int $facilityId, string $date, string $time): array
    {
        $layout = $this->resolveLayout($facilityId, $date, $time);

        if (! $layout) {
            return [];
        }

        return $layout->segments()
            ->where('is_available', true)
            ->orderBy('sort_order')
            ->get()
            ->toArray();
    }

    /**
     * Validate that no overlapping layout exists for the same facility/time slot.
     */
    private function validateNoOverlap(array $data, ?int $excludeId = null): void
    {
        // Overlap is a property of the facility, not of the reader: two active
        // layouts on the same pitch at the same hour conflict whichever branch
        // is selected. Scoping this existence check would let a second,
        // overlapping layout be created because the first one was hidden.
        $query = SpaceLayout::withoutBranchScope()
            ->where('facility_id', $data['facility_id'])
            ->where('is_active', true)
            ->where('start_time', '<', $data['end_time'])
            ->where('end_time', '>', $data['start_time']);

        if ($excludeId) {
            $query->where('id', '!=', $excludeId);
        }

        // Both keys are optional — createDefaultLayout() passes neither, so this
        // raised "Undefined array key" on every facility ever created, which is a
        // warning in the app and a hard error under PHPUnit.
        //
        // NOTE: a null day here still compiles to `effective_day_of_week = NULL`,
        // which SQL never satisfies, so an all-days layout is currently exempt
        // from overlap detection. Left as-is deliberately: every tenant already
        // has an auto-created all-days default layout, and making it collide
        // would start refusing temporal layouts that can be added today.
        if ($data['is_recurring'] ?? true) {
            $query->where('is_recurring', true)
                  ->where('effective_day_of_week', $data['effective_day_of_week'] ?? null);
        } else {
            $query->where('is_recurring', false)
                  ->where('effective_date', $data['effective_date'] ?? null);
        }

        if ($query->exists()) {
            throw new DomainException('يوجد تخطيط متداخل في نفس الفترة الزمنية');
        }
    }

    /**
     * Auto-generate segments based on layout type and config.
     */
    private function generateSegments(SpaceLayout $layout): void
    {
        $type = $layout->layout_type->value;
        $config = $layout->layout_config;

        $segments = match ($type) {
            'grid' => $this->generateGridSegments($config),
            'lanes' => $this->generateLaneSegments($config),
            'zones' => $this->generateZoneSegments($config),
            'custom' => $this->generateCustomSegments($config),
            default => [],
        };

        foreach ($segments as $index => $segment) {
            SpaceSegment::create([
                'space_layout_id' => $layout->id,
                'sort_order' => $index,
                ...$segment,
            ]);
        }
    }

    /**
     * Grid: rows x columns → R1C1, R1C2, R2C1, etc.
     */
    private function generateGridSegments(array $config): array
    {
        $rows = $config['rows'] ?? 2;
        $cols = $config['columns'] ?? 2;
        $segments = [];

        for ($r = 1; $r <= $rows; $r++) {
            for ($c = 1; $c <= $cols; $c++) {
                $code = "R{$r}C{$c}";
                $segments[] = [
                    'code' => $code,
                    'name' => "Row {$r} Col {$c}",
                    'name_ar' => "صف {$r} عمود {$c}",
                    'row_index' => $r,
                    'col_index' => $c,
                ];
            }
        }

        return $segments;
    }

    /**
     * Lanes: lane_count → Lane 1, Lane 2, etc.
     */
    private function generateLaneSegments(array $config): array
    {
        $count = $config['lane_count'] ?? 4;
        $segments = [];

        for ($i = 1; $i <= $count; $i++) {
            $segments[] = [
                'code' => "L{$i}",
                'name' => "Lane {$i}",
                'name_ar' => "حارة {$i}",
                'lane_number' => $i,
            ];
        }

        return $segments;
    }

    /**
     * Zones: definitions array → one segment per zone definition.
     */
    private function generateZoneSegments(array $config): array
    {
        $definitions = $config['definitions'] ?? [];
        $segments = [];

        foreach ($definitions as $index => $def) {
            $segments[] = [
                'code' => $def['code'] ?? 'Z' . ($index + 1),
                'name' => $def['name'] ?? 'Zone ' . ($index + 1),
                'name_ar' => $def['name_ar'] ?? 'منطقة ' . ($index + 1),
                'area_sqm' => $def['area_sqm'] ?? null,
                'capacity' => $def['capacity'] ?? null,
            ];
        }

        return $segments;
    }

    /**
     * Custom: same as zones (no geometric constraints).
     */
    private function generateCustomSegments(array $config): array
    {
        return $this->generateZoneSegments($config);
    }
}
