<?php

namespace Modules\Attendance\Services;

use App\Models\Attendance;
use App\Models\Scopes\CompanyScope;
use App\Models\User;
use App\Models\WorkDuration;
use App\Services\Attendance\AttendancePunchDecision;
use App\Services\Attendance\AttendancePunchValidator;
use Carbon\Carbon;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class AttendanceService
{
    public function __construct(
        protected AttendancePunchValidator $punchValidator
    ) {}

    public function clockIn(User $user, array $meta = []): Attendance
    {
        $decision = $this->validatePunch($user, $meta);
        $today = Carbon::today()->toDateString();
        $existing = Attendance::withoutGlobalScope(CompanyScope::class)
            ->where('company_id', $user->company_id)
            ->where('user_id', $user->id)
            ->where('date', $today)
            ->first();

        if ($existing && $existing->clock_in_date_time) {
            throw new \RuntimeException('You have already checked in.');
        }

        $now = Carbon::now();
        $selfiePath = $this->storeSelfie(
            $user,
            $meta['selfie'] ?? null,
            'in',
            $today
        );

        $clockInPayload = array_merge($decision->clockInAttributes(), [
            'clock_in_time' => $now->format('H:i:s'),
            'clock_in_date_time' => $now,
            'clock_in_ip_address' => $meta['ip'] ?? null,
            'clock_in_selfie_path' => $selfiePath,
            'status' => 'present',
        ]);

        // Day row may already exist (leave/admin/biometric) without a punch.
        if ($existing) {
            $existing->update($clockInPayload);

            $hasOpenDuration = WorkDuration::where('attendance_id', $existing->id)
                ->whereNull('end_time')
                ->exists();
            if (! $hasOpenDuration) {
                WorkDuration::create([
                    'company_id' => $user->company_id,
                    'attendance_id' => $existing->id,
                    'start_time' => $now->format('H:i:s'),
                    'status' => 'started',
                ]);
            }

            return $existing->fresh();
        }

        $attendance = Attendance::create(array_merge([
            'company_id' => $user->company_id,
            'user_id' => $user->id,
            'date' => $today,
        ], $clockInPayload));

        WorkDuration::create([
            'company_id' => $user->company_id,
            'attendance_id' => $attendance->id,
            'start_time' => $now->format('H:i:s'),
            'status' => 'started',
        ]);

        return $attendance;
    }

    public function clockOut(User $user, array $meta = []): Attendance
    {
        $today = Carbon::today()->toDateString();
        $attendance = Attendance::withoutGlobalScope(CompanyScope::class)
            ->where('company_id', $user->company_id)
            ->where('user_id', $user->id)
            ->where('date', $today)
            ->whereNull('clock_out_date_time')
            ->first();

        if (! $attendance || ! $attendance->clock_in_date_time) {
            throw new \RuntimeException('You must check in before checking out.');
        }

        $decision = $this->punchValidator->validateCheckOut(
            $user,
            $attendance,
            $meta,
            $meta['method'] ?? AttendancePunchValidator::METHOD_MOBILE
        );

        $now = Carbon::now();
        $selfiePath = $this->storeSelfie(
            $user,
            $meta['selfie'] ?? null,
            'out',
            $today
        );

        $attendance->update(array_merge($decision->clockOutAttributes(), [
            'clock_out_time' => $now->format('H:i:s'),
            'clock_out_date_time' => $now,
            'clock_out_ip_address' => $meta['ip'] ?? null,
            'clock_out_selfie_path' => $selfiePath,
            'total_duration' => $attendance->clock_in_date_time->diffInSeconds($now),
        ]));

        $wd = WorkDuration::where('attendance_id', $attendance->id)->whereNull('end_time')->first();
        if ($wd) {
            $wd->update([
                'end_time' => $now->format('H:i:s'),
                'duration' => $attendance->clock_in_date_time->diffInSeconds($now),
            ]);
        }

        return $attendance->fresh();
    }

    /**
     * @param  array<string, mixed>  $meta
     */
    private function validatePunch(User $user, array $meta): AttendancePunchDecision
    {
        return $this->punchValidator->validate(
            $user,
            $meta,
            $meta['method'] ?? AttendancePunchValidator::METHOD_MOBILE
        );
    }

    private function storeSelfie(User $user, ?string $selfie, string $kind, string $date): ?string
    {
        if ($selfie === null || trim($selfie) === '') {
            return null;
        }

        $binary = $this->decodeSelfieBinary($selfie);
        if ($binary === null) {
            return null;
        }

        $relativePath = sprintf(
            'attendance-selfies/%s/%s/%s_%s.jpg',
            $user->company_id ?? '0',
            $user->id,
            $date,
            $kind
        );

        Storage::disk('public')->put($relativePath, $binary);

        return $relativePath;
    }

    private function decodeSelfieBinary(string $selfie): ?string
    {
        $payload = $selfie;
        if (Str::contains($selfie, ',')) {
            $payload = Str::after($selfie, ',');
        }

        $decoded = base64_decode($payload, true);
        if ($decoded === false || $decoded === '') {
            return null;
        }

        return $decoded;
    }
}
