<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Attendance;
use App\Models\User;
use App\Models\UserFace;
use Modules\Attendance\Services\AttendanceService;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;

class MobileCompatController extends Controller
{
    private const FACE_MATCH_THRESHOLD = 16;

    public function listUsers(Request $request): JsonResponse
    {
        $actor = $request->user();
        $query = User::query()
            ->forActiveDirectory()
            ->with(['department:id,name', 'location:id,name', 'face:id,user_id']);

        if ($actor->company_id) {
            $query->where('company_id', $actor->company_id);
        } else {
            $query->where('id', $actor->id);
        }

        if (! $actor->isTenantManager()) {
            $query->where('id', $actor->id);
        }

        $users = $query
            ->orderBy('name')
            ->get()
            ->map(fn (User $row) => [
                'id' => $row->id,
                'name' => $row->name,
                'email' => $row->email,
                'employee_number' => $row->employee_number,
                'company_id' => $row->company_id,
                'location' => $row->location ? [
                    'id' => $row->location->id,
                    'name' => $row->location->name,
                ] : null,
                'department' => $row->department ? [
                    'id' => $row->department->id,
                    'name' => $row->department->name,
                ] : null,
                'has_face_id' => (bool) $row->face,
            ])->values();

        return response()->json($users);
    }

    public function todayAttendanceDetails(Request $request): JsonResponse
    {
        if (! $request->user()->isWorkforceMember()) {
            return response()->json(['message' => 'Attendance is only available for employee accounts.'], 403);
        }
        $today = now()->toDateString();
        $attendance = Attendance::query()
            ->where('user_id', $request->user()->id)
            ->where('date', $today)
            ->first();

        if (! $attendance) {
            return response()->json([
                'date' => $today,
                'status' => 'not_clocked_in',
                'clock_in_time' => null,
                'clock_out_time' => null,
                'clock_in_date_time' => null,
                'clock_out_date_time' => null,
            ]);
        }

        return response()->json([
            'id' => $attendance->id,
            'date' => $attendance->date?->toDateString(),
            'status' => $attendance->status,
            'clock_in_time' => $attendance->clock_in_time,
            'clock_out_time' => $attendance->clock_out_time,
            'clock_in_date_time' => $attendance->clock_in_date_time,
            'clock_out_date_time' => $attendance->clock_out_date_time,
            'total_duration' => $attendance->total_duration,
        ]);
    }

    public function summaryMonth(Request $request): JsonResponse
    {
        if (! $request->user()->isWorkforceMember()) {
            return response()->json(['message' => 'Attendance is only available for employee accounts.'], 403);
        }
        $validated = $request->validate([
            'month' => 'required|integer|min:1|max:12',
            'year' => 'required|integer|min:2000|max:2100',
        ]);

        $start = Carbon::createFromDate((int) $validated['year'], (int) $validated['month'], 1)->startOfMonth();
        $end = (clone $start)->endOfMonth();

        $records = Attendance::query()
            ->where('user_id', $request->user()->id)
            ->whereBetween('date', [$start->toDateString(), $end->toDateString()])
            ->orderByDesc('date')
            ->get();

        $summary = [
            'total_days' => $records->count(),
            'present_days' => $records->where('status', 'present')->count(),
            'late_days' => $records->where('is_late', true)->count(),
            'half_days' => $records->where('is_half_day', true)->count(),
            'total_minutes' => (int) $records->sum(fn (Attendance $item) => (int) ($item->total_duration ?? 0)),
        ];

        return response()->json([
            'month' => (int) $validated['month'],
            'year' => (int) $validated['year'],
            'summary' => $summary,
            'records' => $records->values(),
        ]);
    }

    public function usersMissingFaces(Request $request): JsonResponse
    {
        $user = $request->user();
        $query = User::query()
            ->forActiveDirectory()
            ->with('face');

        if ($user->company_id) {
            $query->where('company_id', $user->company_id);
        } else {
            $query->where('id', $user->id);
        }

        if (! $user->isTenantManager()) {
            $query->where('id', $user->id);
        }

        $users = $query
            ->get()
            ->filter(fn (User $row) => ! $row->face)
            ->values()
            ->map(fn (User $row) => [
                'id' => $row->id,
                'name' => $row->name,
                'email' => $row->email,
                'employee_number' => $row->employee_number,
                'company_id' => $row->company_id,
                'has_face_id' => false,
            ]);

        return response()->json($users);
    }

    public function showUser(Request $request, int $id): JsonResponse
    {
        $query = User::query()->forActiveDirectory()->with(['department', 'designation', 'location', 'face']);
        $actor = $request->user();
        if ($actor->company_id) {
            $query->where('company_id', $actor->company_id);
        }

        if (! $actor->isTenantManager()) {
            $query->where('id', $actor->id);
        }

        $user = $query->findOrFail($id);

        return response()->json([
            ...$user->toArray(),
            'has_face_id' => (bool) $user->face,
        ]);
    }

    public function faceSync(Request $request): JsonResponse
    {
        $actor = $request->user();
        $query = UserFace::query()->with('user:id,name,email,employee_number,company_id');

        if ($actor->company_id) {
            $query->where('company_id', $actor->company_id);
        } else {
            $query->where('user_id', $actor->id);
        }

        if (! $actor->isTenantManager()) {
            $query->where('user_id', $actor->id);
        }

        return response()->json($query->get());
    }

    public function faceRegister(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'user_id' => 'nullable|integer|exists:users,id',
            'face_data' => 'required|string',
            'overwrite' => 'nullable|boolean',
            'is_update' => 'nullable|boolean',
        ]);

        $actor = $request->user();
        $targetUserId = (int) ($validated['user_id'] ?? $actor->id);
        $targetUser = User::query()->findOrFail($targetUserId);

        if ($actor->company_id && $targetUser->company_id !== $actor->company_id) {
            return response()->json(['message' => 'Target user is outside your tenant.'], 403);
        }

        if (! $targetUser->isWorkforceMember()) {
            return response()->json(['message' => 'Face registration is only for employee accounts.'], 403);
        }

        if (! $actor->isTenantManager() && $targetUser->id !== $actor->id) {
            return response()->json(['message' => 'You can register face only for yourself.'], 403);
        }

        $face = UserFace::query()->firstOrNew([
            'company_id' => $targetUser->company_id,
            'user_id' => $targetUser->id,
        ]);

        $payload = $this->extractBase64Payload($validated['face_data']);
        $imageBinary = $this->decodeBase64Image($payload);
        if (! $imageBinary) {
            throw ValidationException::withMessages(['face_data' => ['Invalid face image payload.']]);
        }

        $face->face_data = $validated['face_data'];
        $face->face_hash = $this->buildFaceHash($imageBinary);
        $face->created_by = $actor->id;
        $face->save();

        return response()->json([
            'message' => 'Face data saved.',
            'face_id' => $face->id,
            'user_id' => $face->user_id,
            'company_id' => $face->company_id,
        ], 201);
    }

    public function faceLogin(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'face_data' => 'required|string',
            'company_id' => 'nullable|integer',
            'email' => 'nullable|email',
        ]);

        $payload = $this->extractBase64Payload($validated['face_data']);
        $imageBinary = $this->decodeBase64Image($payload);
        if (! $imageBinary) {
            throw ValidationException::withMessages(['face_data' => ['Invalid face image payload.']]);
        }

        $incomingHash = $this->buildFaceHash($imageBinary);
        $rawPayload = trim($payload);

        $facesQuery = UserFace::query()->with('user');
        if (! empty($validated['company_id'])) {
            $facesQuery->where('company_id', (int) $validated['company_id']);
        }
        if (! empty($validated['email'])) {
            $email = (string) $validated['email'];
            $facesQuery->whereHas('user', fn ($query) => $query->where('email', $email));
        }

        $faces = $facesQuery->get();
        if ($faces->isEmpty()) {
            return response()->json(['message' => 'No registered face found for matching.'], 404);
        }

        [$bestFace, $bestDistance] = $this->findBestFaceMatch(
            $faces,
            $incomingHash,
            $rawPayload
        );

        if (! $bestFace || $bestDistance > self::FACE_MATCH_THRESHOLD) {
            return response()->json(['message' => 'Face not recognized.'], 401);
        }

        $user = $bestFace->user;
        if (! $user) {
            return response()->json(['message' => 'Matched user not found.'], 404);
        }
        if (! ($user->login_enabled && $user->allow_login)) {
            return response()->json(['message' => 'Login is disabled for this account.'], 403);
        }

        if (! $user->isWorkforceMember()) {
            return response()->json(['message' => 'Face login is only available for employee accounts.'], 403);
        }

        $token = $user->createToken('api')->plainTextToken;

        return response()->json([
            'user' => $user->load(['company', 'role', 'department', 'designation']),
            'token' => $token,
            'token_type' => 'Bearer',
            'match_distance' => $bestDistance,
        ]);
    }

    public function faceAttendance(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'face_data' => 'required|string',
            'mode' => 'required|in:login,logout',
            'company_id' => 'nullable|integer',
            'email' => 'nullable|email',
            'latitude' => 'nullable|numeric',
            'longitude' => 'nullable|numeric',
        ]);

        $payload = $this->extractBase64Payload($validated['face_data']);
        $imageBinary = $this->decodeBase64Image($payload);
        if (! $imageBinary) {
            throw ValidationException::withMessages(['face_data' => ['Invalid face image payload.']]);
        }

        $incomingHash = $this->buildFaceHash($imageBinary);
        $rawPayload = trim($payload);

        $facesQuery = UserFace::query()->with('user');
        if (! empty($validated['company_id'])) {
            $facesQuery->where('company_id', (int) $validated['company_id']);
        }
        if (! empty($validated['email'])) {
            $email = (string) $validated['email'];
            $facesQuery->whereHas('user', fn ($query) => $query->where('email', $email));
        }

        $faces = $facesQuery->get();
        if ($faces->isEmpty()) {
            return response()->json(['message' => 'Face not registered. Please use a registered face.'], 404);
        }

        [$bestFace, $bestDistance] = $this->findBestFaceMatch(
            $faces,
            $incomingHash,
            $rawPayload
        );

        if (! $bestFace || $bestDistance > self::FACE_MATCH_THRESHOLD) {
            return response()->json(['message' => 'Face not registered. Please use a registered face.'], 422);
        }

        $user = $bestFace->user;
        if (! $user || ! ($user->login_enabled && $user->allow_login)) {
            return response()->json(['message' => 'Login is disabled for this account.'], 403);
        }

        if (! $user->isWorkforceMember()) {
            return response()->json(['message' => 'Face attendance is only available for employee accounts.'], 403);
        }

        /** @var AttendanceService $service */
        $service = app(AttendanceService::class);
        $meta = [
            'ip' => $request->ip(),
            'latitude' => $validated['latitude'] ?? null,
            'longitude' => $validated['longitude'] ?? null,
        ];

        try {
            if ($validated['mode'] === 'login') {
                $attendance = $service->clockIn($user, $meta);
            } else {
                $attendance = $service->clockOut($user, $meta);
            }
        } catch (\RuntimeException $exception) {
            return response()->json(['message' => $exception->getMessage()], 422);
        }

        $capturedAt = now();

        return response()->json([
            'message' => $validated['mode'] === 'login'
                ? 'Login captured successfully.'
                : 'Logout captured successfully.',
            'mode' => $validated['mode'],
            'captured_at' => $capturedAt->toIso8601String(),
            'match_distance' => $bestDistance,
            'user' => [
                'id' => $user->id,
                'name' => $user->name,
                'email' => $user->email,
                'employee_number' => $user->employee_number,
            ],
            'attendance' => [
                'id' => $attendance->id,
                'date' => $attendance->date?->toDateString(),
                'status' => $attendance->status,
                'clock_in_time' => $attendance->clock_in_time,
                'clock_out_time' => $attendance->clock_out_time,
                'total_duration' => $attendance->total_duration,
            ],
        ]);
    }

    private function extractBase64Payload(string $value): string
    {
        if (str_contains($value, ',')) {
            $parts = explode(',', $value, 2);
            return $parts[1] ?? '';
        }

        return $value;
    }

    private function decodeBase64Image(string $payload): ?string
    {
        $decoded = base64_decode($payload, true);
        return $decoded === false ? null : $decoded;
    }

    private function buildFaceHash(string $imageBinary): ?string
    {
        if (! function_exists('imagecreatefromstring') || ! function_exists('imagescale')) {
            return null;
        }

        $img = @imagecreatefromstring($imageBinary);
        if (! $img) {
            return null;
        }

        $small = @imagescale($img, 8, 8, IMG_BILINEAR_FIXED);
        if (! $small) {
            imagedestroy($img);
            return null;
        }

        $grays = [];
        for ($y = 0; $y < 8; $y++) {
            for ($x = 0; $x < 8; $x++) {
                $rgb = imagecolorat($small, $x, $y);
                $r = ($rgb >> 16) & 0xFF;
                $g = ($rgb >> 8) & 0xFF;
                $b = $rgb & 0xFF;
                $grays[] = (int) round(($r + $g + $b) / 3);
            }
        }

        imagedestroy($small);
        imagedestroy($img);

        if (empty($grays)) {
            return null;
        }

        $avg = array_sum($grays) / count($grays);
        $bits = array_map(fn ($gray) => $gray >= $avg ? '1' : '0', $grays);
        return implode('', $bits);
    }

    private function hammingDistance(string $left, string $right): int
    {
        $length = min(strlen($left), strlen($right));
        $distance = 0;
        for ($i = 0; $i < $length; $i++) {
            if ($left[$i] !== $right[$i]) {
                $distance++;
            }
        }

        return $distance + abs(strlen($left) - strlen($right));
    }

    /**
     * @param \Illuminate\Support\Collection<int, UserFace> $faces
     * @return array{0: ?UserFace, 1: int}
     */
    private function findBestFaceMatch($faces, ?string $incomingHash, string $rawPayload): array
    {
        $bestFace = null;
        $bestDistance = PHP_INT_MAX;

        foreach ($faces as $face) {
            $candidateHash = $face->face_hash;
            if (empty($candidateHash) && ! empty($face->face_data)) {
                $storedPayload = $this->extractBase64Payload((string) $face->face_data);
                $storedBinary = $this->decodeBase64Image($storedPayload);
                if ($storedBinary) {
                    $candidateHash = $this->buildFaceHash($storedBinary);
                }
            }

            if ($incomingHash && empty($candidateHash)) {
                continue;
            }

            if ($incomingHash) {
                $distance = $this->hammingDistance($incomingHash, (string) $candidateHash);
            } else {
                $storedPayload = trim($this->extractBase64Payload((string) ($face->face_data ?? '')));
                $distance = $storedPayload === $rawPayload ? 0 : 64;
            }

            if ($distance < $bestDistance) {
                $bestDistance = $distance;
                $bestFace = $face;
            }
        }

        return [$bestFace, $bestDistance];
    }
}

