<?php

namespace App\Services\Biometric;

use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class ZktecoPayloadParser
{
    /**
     * @return array{rows: list<array<string, mixed>>, error: ?string}
     */
    public function parseRequest(Request $request): array
    {
        $candidates = [];

        $json = $request->json()->all();
        if ($json !== []) {
            $candidates[] = $json;
        }

        $raw = trim((string) $request->getContent());
        if ($raw !== '') {
            $decoded = $this->decodeBody($raw);
            if ($decoded !== null) {
                $candidates[] = $decoded;
            }
        }

        foreach (['data', 'payload', 'records', 'Attendance', 'attendance'] as $key) {
            if ($request->filled($key)) {
                $value = $request->input($key);
                if (is_string($value)) {
                    $decoded = $this->decodeBody($value);
                    if ($decoded !== null) {
                        $candidates[] = $decoded;
                    }
                } elseif (is_array($value)) {
                    $candidates[] = $value;
                }
            }
        }

        foreach ($candidates as $payload) {
            $rows = $this->extractRows($payload);
            if ($rows !== []) {
                return [
                    'rows' => array_map(fn (array $row) => $this->normalizeRow($row), $rows),
                    'error' => null,
                ];
            }
        }

        if ($raw !== '' && $json === []) {
            Log::warning('zkteco.payload_parse_failed', [
                'ip' => $request->ip(),
                'content_type' => $request->header('Content-Type'),
                'body_preview' => mb_substr($raw, 0, 500),
            ]);

            return ['rows' => [], 'error' => 'Invalid payload. Expected JSON array of punches.'];
        }

        return ['rows' => [], 'error' => 'Expected a JSON array of punches.'];
    }

    /**
     * @return list<array<string, mixed>>
     */
    protected function extractRows(mixed $payload): array
    {
        if (! is_array($payload)) {
            return [];
        }

        if (isset($payload['data']) && is_array($payload['data'])) {
            return $this->extractRows($payload['data']);
        }

        if ($this->isList($payload)) {
            $rows = [];
            foreach ($payload as $item) {
                if (is_array($item)) {
                    $rows[] = $item;
                }
            }

            return $rows;
        }

        // Single associative punch object
        if ($this->looksLikePunch($payload)) {
            return [$payload];
        }

        return [];
    }

    /**
     * @param  array<string, mixed>  $row
     * @return array<string, mixed>
     */
    public function normalizeRow(array $row): array
    {
        $normalized = [];
        foreach ($row as $key => $value) {
            $normalized[strtolower(trim((string) $key))] = $value;
        }

        $aliases = [
            'emp_code' => ['emp_code', 'employee_code', 'employee_id', 'empcode', 'pin', 'userid', 'user_id'],
            'punch_datetime' => ['punch_datetime', 'punch_time_date', 'checktime', 'check_time', 'att_time', 'datetime'],
            'punch_date' => ['punch_date', 'checkdate', 'att_date', 'date'],
            'punch_time' => ['punch_time', 'checktime_only', 'time'],
            'punch_state' => ['punch_state', 'punch_status', 'state', 'status', 'inout', 'in_out', 'checktype', 'check_type'],
            'verify_type' => ['verify_type', 'verifytype', 'verify'],
            'terminal_sn' => ['terminal_sn', 'sn', 'serial_number', 'device_sn'],
            'terminal_alias' => ['terminal_alias', 'device_name', 'alias'],
            'longitude' => ['longitude', 'lng', 'lon'],
            'latitude' => ['latitude', 'lat'],
            'gps_location' => ['gps_location', 'gps', 'location'],
            'upload_time' => ['upload_time', 'uploadtime'],
            'temperature' => ['temperature'],
            'mask_flag' => ['mask_flag', 'mask'],
            'company_name' => ['company_name', 'company'],
            'first_name' => ['first_name', 'firstname'],
            'last_name' => ['last_name', 'lastname'],
            'device_transaction_id' => [
                'device_transaction_id', 'transaction_id', 'txn_id', 'att_id',
                'record_id', 'log_id', 'uuid',
            ],
        ];

        $out = $normalized;
        foreach ($aliases as $canonical => $keys) {
            if (isset($out[$canonical]) && $out[$canonical] !== '' && $out[$canonical] !== null) {
                continue;
            }
            foreach ($keys as $key) {
                if (array_key_exists($key, $normalized) && $normalized[$key] !== '' && $normalized[$key] !== null) {
                    $out[$canonical] = $normalized[$key];
                    break;
                }
            }
        }

        // Combine date + time when datetime missing
        if (empty($out['punch_datetime']) && ! empty($out['punch_date']) && ! empty($out['punch_time'])) {
            $out['punch_datetime'] = trim((string) $out['punch_date']).' '.trim((string) $out['punch_time']);
        }

        // Devices often send numeric punch_state / emp_code — normalize to strings.
        foreach (['emp_code', 'punch_state', 'punch_datetime', 'terminal_sn'] as $stringField) {
            if (array_key_exists($stringField, $out) && $out[$stringField] !== null && ! is_string($out[$stringField])) {
                $out[$stringField] = trim((string) $out[$stringField]);
            }
        }

        return $out;
    }

    public function parsePunchDateTime(string $raw, string $timezone): ?Carbon
    {
        $raw = trim($raw);
        if ($raw === '') {
            return null;
        }

        $formats = [
            'Y-m-d H:i:s.u',
            'Y-m-d H:i:s',
            'Y-m-d H:i',
            'd-m-Y H:i:s',
            'd-m-Y H:i',
            'd/m/Y H:i:s',
            'd/m/Y H:i',
            'Y/m/d H:i:s',
            'm-d-Y H:i:s',
            'Y-m-d\TH:i:s',
            'Y-m-d\TH:i:s.u',
        ];

        foreach ($formats as $format) {
            try {
                $dt = Carbon::createFromFormat($format, $raw, $timezone);
                if ($dt !== false) {
                    return $dt;
                }
            } catch (\Throwable) {
                // try next
            }
        }

        try {
            return Carbon::parse($raw, $timezone);
        } catch (\Throwable) {
            return null;
        }
    }

    protected function decodeBody(string $raw): mixed
    {
        $raw = trim($raw);
        if ($raw === '') {
            return null;
        }

        $decoded = json_decode($raw, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            return $decoded;
        }

        // Device note / Python-style single quotes → attempt JSON conversion
        $asJson = preg_replace("/'/", '"', $raw);
        if (is_string($asJson)) {
            $decoded = json_decode($asJson, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $decoded;
            }
        }

        return null;
    }

    /**
     * @param  array<mixed>  $value
     */
    protected function isList(array $value): bool
    {
        if ($value === []) {
            return true;
        }

        return array_keys($value) === range(0, count($value) - 1);
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    protected function looksLikePunch(array $payload): bool
    {
        $keys = array_map(fn ($k) => strtolower((string) $k), array_keys($payload));

        return (bool) array_intersect($keys, [
            'emp_code', 'employee_code', 'employee_id', 'pin', 'userid',
            'punch_datetime', 'checktime', 'punch_date', 'punch_state',
        ]);
    }
}
