<?php

namespace Modules\Attendance\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Attendance;
use App\Models\AttendancePolicy;
use App\Models\User;
use App\Services\Attendance\AttendancePolicyResolver;
use App\Services\Attendance\AttendancePunchValidator;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Modules\Attendance\Services\AttendanceService;

class AttendanceController extends Controller
{
    public function __construct(
        protected AttendancePolicyResolver $policyResolver,
        protected AttendancePunchValidator $punchValidator
    ) {}

    public function index(Request $request): JsonResponse
    {
        $query = Attendance::with('user')->where('user_id', $request->user()->id);
        if ($request->has('from')) {
            $query->where('date', '>=', $request->from);
        }
        if ($request->has('to')) {
            $query->where('date', '<=', $request->to);
        }
        $attendances = $query->orderByDesc('date')->paginate($request->input('per_page', 15));

        return response()->json($attendances);
    }

    public function today(Request $request): JsonResponse
    {
        $user = $request->user();
        $today = now()->toDateString();
        $attendance = Attendance::with(['clockInOffice', 'clockOutOffice'])
            ->where('user_id', $user->id)
            ->where('date', $today)
            ->first();

        $payload = $attendance
            ? $attendance->toArray()
            : ['date' => $today, 'status' => 'not_clocked_in'];

        $payload['policy'] = $this->policyPayload($user);
        if ($attendance && $attendance->clock_in_date_time && ! $attendance->clock_out_date_time) {
            $payload['working_duration_seconds'] = $attendance->clock_in_date_time->diffInSeconds(now());
        }

        return response()->json($payload);
    }

    public function policy(Request $request): JsonResponse
    {
        return response()->json($this->policyPayload($request->user()));
    }

    public function settings(Request $request): JsonResponse
    {
        $policy = $this->policyResolver->forCompany((int) $request->user()->company_id);

        return response()->json($policy);
    }

    public function updateSettings(Request $request): JsonResponse
    {
        $companyId = (int) $request->user()->company_id;
        $validated = $request->validate([
            'mode' => ['required', Rule::in(AttendancePolicy::MODES)],
            'remote_approval_required' => ['sometimes', 'boolean'],
            'default_radius_meters' => ['sometimes', 'integer', 'min:10', 'max:10000'],
            'gps_accuracy_requirement' => ['nullable', 'integer', 'min:1', 'max:1000'],
            'require_location_for_office' => ['sometimes', 'boolean'],
            'allow_web_checkin' => ['sometimes', 'boolean'],
            'allow_mobile_checkin' => ['sometimes', 'boolean'],
        ]);

        $flags = AttendancePolicy::syncDerivedFlags($validated['mode']);
        $policy = $this->policyResolver->forCompany($companyId);
        $policy->fill(array_merge($validated, $flags));
        $policy->company_id = $companyId;
        $policy->save();

        return response()->json($policy->fresh());
    }

    public function clockIn(Request $request): JsonResponse
    {
        $validated = $this->punchRules($request);

        try {
            $service = app(AttendanceService::class);
            $attendance = $service->clockIn($request->user(), array_merge($validated, [
                'ip' => $request->ip(),
                'method' => AttendancePunchValidator::METHOD_MOBILE,
            ]));

            return response()->json($attendance->load(['workDurations', 'clockInOffice']), 201);
        } catch (\RuntimeException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function clockOut(Request $request): JsonResponse
    {
        $validated = $this->punchRules($request);

        try {
            $service = app(AttendanceService::class);
            $attendance = $service->clockOut($request->user(), array_merge($validated, [
                'ip' => $request->ip(),
                'method' => AttendancePunchValidator::METHOD_MOBILE,
            ]));

            return response()->json($attendance->load(['workDurations', 'clockOutOffice']));
        } catch (\RuntimeException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }
    }

    public function show(Attendance $attendance): JsonResponse
    {
        $user = request()->user();
        if ((int) $attendance->company_id !== (int) $user->company_id) {
            abort(403);
        }
        if (! in_array((int) $attendance->user_id, $user->visibleStaffUserIds(), true)) {
            abort(403);
        }

        return response()->json($attendance->load(['user', 'workDurations', 'clockInOffice', 'clockOutOffice']));
    }

    /**
     * @return array<string, mixed>
     */
    private function punchRules(Request $request): array
    {
        return $request->validate([
            'latitude' => 'nullable|numeric|between:-90,90',
            'longitude' => 'nullable|numeric|between:-180,180',
            'accuracy' => 'nullable|numeric|min:0',
            'selfie' => 'required|string',
            'location' => 'nullable|string|max:500',
            'attendance_type' => 'nullable|in:office,remote,normal',
            'remote_reason' => 'nullable|string|max:500',
            'is_mocked' => 'nullable|boolean',
        ]);
    }

    /**
     * @return array<string, mixed>
     */
    private function policyPayload(User $user): array
    {
        $resolved = $this->policyResolver->resolveForUser($user);
        $offices = $this->punchValidator->activeGeofencedOffices((int) ($user->company_id ?? 0))
            ->map(fn ($office) => [
                'id' => $office->id,
                'name' => $office->name,
                'latitude' => $office->latitude,
                'longitude' => $office->longitude,
                'radius_meters' => $office->radius_meters ?: $resolved->defaultRadiusMeters,
                'is_assigned' => (int) $office->id === (int) $user->location_id,
            ])
            ->values()
            ->all();

        return array_merge($resolved->toArray(), [
            'offices' => $offices,
        ]);
    }
}
