<?php

namespace App\Http\Controllers\Api\V1\Admin;

use App\Exceptions\DomainException;
use App\Http\Controllers\Controller;
use App\Models\DoctorLocationAssignment;
use App\Models\DoctorScheduleException;
use App\Models\DoctorWorkingHour;
use App\Services\AuditService;
use App\Support\ApiResponse;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;

class ScheduleAdminController extends Controller
{
    use ApiResponse;

    public function __construct(private readonly AuditService $audit) {}

    public function show(Request $request, DoctorLocationAssignment $assignment)
    {
        $this->guard($request, $assignment);

        return $this->success([
            'assignment' => $assignment->load(['doctor.user', 'location']),
            'working_hours' => $assignment->workingHours()->orderBy('weekday')->orderBy('start_time')->get(),
            'exceptions' => $assignment->scheduleExceptions()->orderBy('starts_at')->get(),
        ]);
    }

    public function storeWorkingHour(Request $request, DoctorLocationAssignment $assignment)
    {
        $this->guard($request, $assignment);

        $data = $request->validate([
            'weekday' => ['required', 'integer', 'min:1', 'max:7'],
            'start_time' => ['required', 'date_format:H:i'],
            'end_time' => ['required', 'date_format:H:i', 'after:start_time'],
            'is_active' => ['sometimes', 'boolean'],
        ]);

        $row = $assignment->workingHours()->create($data + [
            'organization_id' => $request->user()->organization_id,
        ]);

        $this->audit->log('schedule.working_hour_created', $request->user(), $row, null, $row->toArray());

        return $this->success($row, 'Working hour created.', 201);
    }

    public function updateWorkingHour(Request $request, DoctorWorkingHour $workingHour)
    {
        if ($workingHour->organization_id !== $request->user()->organization_id) {
            throw new DomainException('FORBIDDEN', 'Working hour belongs to another organization.', 403);
        }

        $data = $request->validate([
            'weekday' => ['sometimes', 'integer', 'min:1', 'max:7'],
            'start_time' => ['sometimes', 'date_format:H:i'],
            'end_time' => ['sometimes', 'date_format:H:i'],
            'is_active' => ['sometimes', 'boolean'],
        ]);

        $start = $data['start_time'] ?? substr((string) $workingHour->start_time, 0, 5);
        $end = $data['end_time'] ?? substr((string) $workingHour->end_time, 0, 5);
        if ($start >= $end) {
            throw new DomainException('INVALID_WORKING_HOURS', 'Working-hour end time must be after start time.', 422);
        }

        $before = $workingHour->toArray();
        $workingHour->update($data);
        $this->audit->log('schedule.working_hour_updated', $request->user(), $workingHour, $before, $workingHour->fresh()->toArray());

        return $this->success($workingHour->fresh(), 'Working hour updated.');
    }

    public function destroyWorkingHour(Request $request, DoctorWorkingHour $workingHour)
    {
        if ($workingHour->organization_id !== $request->user()->organization_id) {
            throw new DomainException('FORBIDDEN', 'Working hour belongs to another organization.', 403);
        }
        $before = $workingHour->toArray();
        $workingHour->delete();
        $this->audit->log('schedule.working_hour_deleted', $request->user(), $workingHour, $before, null);

        return $this->success(null, 'Working hour removed.');
    }

    public function storeException(Request $request, DoctorLocationAssignment $assignment)
    {
        $this->guard($request, $assignment);

        $data = $request->validate([
            'type' => ['required', 'in:leave,holiday,blocked_time,special_hours'],
            'starts_at' => ['required', 'date'],
            'ends_at' => ['required', 'date', 'after:starts_at'],
            'reason' => ['nullable', 'string', 'max:255'],
        ]);

        $row = $assignment->scheduleExceptions()->create([
            'organization_id' => $request->user()->organization_id,
            'type' => $data['type'],
            'starts_at' => CarbonImmutable::parse($data['starts_at'])->utc(),
            'ends_at' => CarbonImmutable::parse($data['ends_at'])->utc(),
            'reason' => $data['reason'] ?? null,
            'created_by_user_id' => $request->user()->id,
        ]);

        $this->audit->log('schedule.exception_created', $request->user(), $row, null, $row->toArray());

        return $this->success($row, 'Schedule exception created.', 201);
    }

    public function updateException(Request $request, DoctorScheduleException $exception)
    {
        if ($exception->organization_id !== $request->user()->organization_id) {
            throw new DomainException('FORBIDDEN', 'Schedule exception belongs to another organization.', 403);
        }

        $data = $request->validate([
            'type' => ['sometimes', 'in:leave,holiday,blocked_time,special_hours'],
            'starts_at' => ['sometimes', 'date'],
            'ends_at' => ['sometimes', 'date'],
            'reason' => ['nullable', 'string', 'max:255'],
        ]);

        $startsAt = isset($data['starts_at'])
            ? CarbonImmutable::parse($data['starts_at'])->utc()
            : CarbonImmutable::instance($exception->starts_at)->utc();
        $endsAt = isset($data['ends_at'])
            ? CarbonImmutable::parse($data['ends_at'])->utc()
            : CarbonImmutable::instance($exception->ends_at)->utc();

        if ($endsAt->lte($startsAt)) {
            throw new DomainException('INVALID_SCHEDULE_EXCEPTION', 'Schedule exception end must be after start.', 422);
        }

        $before = $exception->toArray();
        $exception->update([
            ...array_intersect_key($data, array_flip(['type', 'reason'])),
            'starts_at' => $startsAt,
            'ends_at' => $endsAt,
        ]);

        $this->audit->log('schedule.exception_updated', $request->user(), $exception, $before, $exception->fresh()->toArray());

        return $this->success($exception->fresh(), 'Schedule exception updated.');
    }

    public function destroyException(Request $request, DoctorScheduleException $exception)
    {
        if ($exception->organization_id !== $request->user()->organization_id) {
            throw new DomainException('FORBIDDEN', 'Schedule exception belongs to another organization.', 403);
        }

        $before = $exception->toArray();
        $exception->delete();
        $this->audit->log('schedule.exception_deleted', $request->user(), $exception, $before, null);

        return $this->success(null, 'Schedule exception removed.');
    }

    private function guard(Request $request, DoctorLocationAssignment $assignment): void
    {
        if ($assignment->organization_id !== $request->user()->organization_id) {
            throw new DomainException('FORBIDDEN', 'Assignment belongs to another organization.', 403);
        }
    }
}
