<?php

namespace App\Modules\Academic\Services;

use App\Modules\Academic\Models\DapodikRombelMappingModel;
use App\Modules\Academic\Models\DapodikSyncJobModel;
use App\Modules\Academic\Models\StudentModel;

/**
 * Syncs Dapodik peserta didik into local students with rombel -> class mapping.
 */
class DapodikSyncService
{
    protected DapodikClient $client;
    protected DapodikRombelMappingModel $mappingModel;
    protected StudentModel $studentModel;
    protected DapodikSyncJobModel $jobModel;

    public function __construct(?DapodikClient $client = null, ?DapodikSyncJobModel $jobModel = null)
    {
        $this->client       = $client ?? new DapodikClient();
        $this->mappingModel = new DapodikRombelMappingModel();
        $this->studentModel = new StudentModel();
        $this->jobModel     = $jobModel ?? new DapodikSyncJobModel();
    }

    /**
     * Sync students from Dapodik with job tracking. Paginates getPesertaDidik, upserts per batch.
     *
     * @param int $jobId   Job row id for progress updates
     * @param int $limit   Per-page limit
     * @param int $maxPages Max pages to fetch
     * @return void
     */
    public function syncStudentsWithJob(int $jobId, int $limit = 100, int $maxPages = 50): void
    {
        $start   = 0;
        $page    = 0;
        $now     = date('Y-m-d H:i:s');
        $processedTotal = 0;
        $totalRows     = 0;
        $knownTotal    = false;

        $db = \Config\Database::connect();

        try {
            while ($page < $maxPages) {
                $response = $this->client->getPesertaDidik($start, $limit);
                if (! ($response['success'] ?? false)) {
                    $err = $response['error'] ?? 'Unknown error';
                    $this->jobModel->update($jobId, [
                        'status'      => DapodikSyncJobModel::STATUS_FAILED,
                        'message'     => $err,
                        'finished_at' => $now,
                    ]);
                    return;
                }

                $rows = DapodikClient::normalizePesertaDidikRows($response);
                if ($rows === []) {
                    break;
                }

                if (! $knownTotal) {
                    $results = $response['results'] ?? null;
                    if (is_numeric($results) && (int) $results > 0) {
                        $totalRows    = (int) $results;
                        $knownTotal   = true;
                        $this->jobModel->update($jobId, ['total_rows' => $totalRows]);
                    }
                }

                $db->transStart();
                try {
                    $batchCount = 0;
                    foreach ($rows as $row) {
                        $dapodikId = $this->extractDapodikId($row);
                        $nisn      = $this->extractString($row, ['nisn']);
                        $nama      = $this->extractString($row, ['nama', 'name']);
                        $gender    = $this->extractGender($row);
                        $rombel    = $this->extractString($row, ['rombel', 'nama_rombel', 'rombongan_belajar']);

                        // Require at least one stable identifier
                        if ($dapodikId === null && $nisn === '') {
                            continue;
                        }

                        $mapping = $this->mappingModel->getByRombel($rombel);
                        if ($mapping === null) {
                            $this->mappingModel->insert([
                                'dapodik_rombel' => $rombel,
                                'class_id'       => null,
                                'last_seen_at'   => $now,
                            ]);
                            $classId = null;
                        } else {
                            $this->mappingModel->update($mapping['id'], ['last_seen_at' => $now]);
                            $classId = ! empty($mapping['class_id']) ? (int) $mapping['class_id'] : null;
                        }

                        $data = [
                            'dapodik_id' => $dapodikId,
                            'nisn'       => $nisn,
                            'name'       => $nama !== '' ? $nama : ($nisn !== '' ? 'Siswa ' . $nisn : 'Siswa Dapodik'),
                            'gender'     => $gender,
                            'class_id'   => $classId,
                            'is_active'  => 1,
                        ];

                        // Use upsert so repeated syncs update instead of inserting duplicates
                        $this->studentModel->skipValidation(true);
                        $this->studentModel->upsert($data);
                        $batchCount++;
                    }
                    $db->transComplete();
                } catch (\Throwable $e) {
                    $db->transRollback();
                    $this->jobModel->update($jobId, [
                        'status'      => DapodikSyncJobModel::STATUS_FAILED,
                        'message'     => $e->getMessage(),
                        'finished_at' => $now,
                    ]);
                    return;
                }

                $processedTotal += $batchCount;
                $this->jobModel->update($jobId, [
                    'processed_rows' => $processedTotal,
                ]);

                if (count($rows) < $limit) {
                    if (! $knownTotal) {
                        $this->jobModel->update($jobId, ['total_rows' => $processedTotal]);
                    }
                    break;
                }
                $start += $limit;
                $page++;
            }

            $this->jobModel->update($jobId, [
                'status'      => DapodikSyncJobModel::STATUS_COMPLETED,
                'finished_at' => $now,
            ]);
        } catch (\Throwable $e) {
            $this->jobModel->update($jobId, [
                'status'      => DapodikSyncJobModel::STATUS_FAILED,
                'message'     => $e->getMessage(),
                'finished_at' => date('Y-m-d H:i:s'),
            ]);
        }
    }

    /**
     * Extract Dapodik stable identifier for a student row.
     *
     * @param array $row
     * @return string|null
     */
    private function extractDapodikId(array $row): ?string
    {
        foreach (['peserta_didik_id', 'id', 'pd_id'] as $key) {
            if (isset($row[$key]) && is_scalar($row[$key])) {
                $v = trim((string) $row[$key]);
                if ($v !== '') {
                    return $v;
                }
            }
        }
        return null;
    }

    /**
     * Estimate total rows from first Dapodik response (if available).
     *
     * @param int $limit Per-page limit
     * @return array{total: int, from_api: bool}
     */
    public function estimateTotalStudents(int $limit = 100): array
    {
        $response = $this->client->getPesertaDidik(0, $limit);
        if (! ($response['success'] ?? false)) {
            return ['total' => 0, 'from_api' => false];
        }
        $results = $response['results'] ?? null;
        if (is_numeric($results) && (int) $results > 0) {
            return ['total' => (int) $results, 'from_api' => true];
        }
        $rows = DapodikClient::normalizePesertaDidikRows($response);
        return ['total' => count($rows) > 0 ? count($rows) : 0, 'from_api' => false];
    }

    /**
     * Legacy sync (no job). Kept for backwards compatibility.
     *
     * @param int $limit    Per-page limit
     * @param int $maxPages Max pages to fetch
     * @return array{fetched_total: int, inserted_students: int, updated_students: int, unmapped_students_count: int, mappings_created: int, mappings_seen_updated: int, errors: array<int, string>}
     */
    public function syncStudents(int $limit = 200, int $maxPages = 50): array
    {
        $summary = [
            'fetched_total'           => 0,
            'inserted_students'       => 0,
            'updated_students'        => 0,
            'unmapped_students_count' => 0,
            'mappings_created'        => 0,
            'mappings_seen_updated'   => 0,
            'errors'                  => [],
        ];

        $start   = 0;
        $page    = 0;
        $now     = date('Y-m-d H:i:s');

        while ($page < $maxPages) {
            $response = $this->client->getPesertaDidik($start, $limit);
            if (! ($response['success'] ?? false)) {
                $summary['errors'][] = $response['error'] ?? 'Unknown error';
                break;
            }

            $rows = DapodikClient::normalizePesertaDidikRows($response);
            if ($rows === []) {
                break;
            }

            $summary['fetched_total'] += count($rows);

            foreach ($rows as $row) {
                $nisn   = $this->extractString($row, ['nisn']);
                $nama   = $this->extractString($row, ['nama', 'name']);
                $gender = $this->extractGender($row);
                $rombel = $this->extractString($row, ['rombel', 'nama_rombel', 'rombongan_belajar']);

                if ($nisn === '') {
                    continue;
                }

                $mapping = $this->mappingModel->getByRombel($rombel);
                if ($mapping === null) {
                    $this->mappingModel->insert([
                        'dapodik_rombel' => $rombel,
                        'class_id'       => null,
                        'last_seen_at'   => $now,
                    ]);
                    $summary['mappings_created']++;
                    $classId = null;
                } else {
                    $this->mappingModel->update($mapping['id'], ['last_seen_at' => $now]);
                    $summary['mappings_seen_updated']++;
                    $classId = ! empty($mapping['class_id']) ? (int) $mapping['class_id'] : null;
                }
                if ($classId === null) {
                    $summary['unmapped_students_count']++;
                }

                $existing = $this->studentModel->findByNisn($nisn);
                $data = [
                    'nisn'     => $nisn,
                    'name'     => $nama !== '' ? $nama : 'Siswa ' . $nisn,
                    'gender'   => $gender,
                    'class_id' => $classId,
                    'is_active'=> 1,
                ];

                $this->studentModel->skipValidation(false);
                if ($existing) {
                    $this->studentModel->update($existing->id, $data);
                    $summary['updated_students']++;
                } else {
                    $this->studentModel->insert($data);
                    $summary['inserted_students']++;
                }
            }

            if (count($rows) < $limit) {
                break;
            }
            $start += $limit;
            $page++;
        }

        return $summary;
    }

    private function extractString(array $row, array $keys): string
    {
        foreach ($keys as $k) {
            if (isset($row[$k]) && is_scalar($row[$k])) {
                return trim((string) $row[$k]);
            }
        }
        return '';
    }

    private function extractGender(array $row): ?string
    {
        $v = $row['jenis_kelamin'] ?? $row['gender'] ?? $row['jk'] ?? null;
        if ($v === null || $v === '') {
            return null;
        }
        $v = strtoupper(trim((string) $v));
        if ($v === 'L' || $v === 'LAKI-LAKI' || $v === 'Laki-laki') {
            return 'L';
        }
        if ($v === 'P' || $v === 'PEREMPUAN' || $v === 'Perempuan') {
            return 'P';
        }
        return null;
    }
}
