<?php

declare(strict_types=1);

namespace App\Services\Admin;

use App\Services\Mobile\MobileJsonService;
use CodeIgniter\Database\BaseConnection;
use Config\Database;

/**
 * Login admin memakai Ion Auth CI3: `admin_users` + relasi `admin_users_groups` / `admin_groups`.
 * Password: bcrypt ($2a$/$2y$) atau legacy MD5 (32 hex).
 * Setelah sukses, token API ditulis ke baris pegawai "proxy" (sama seperti sebelumnya).
 */
class AdminUsersLoginService
{
    protected BaseConnection $db;

    public function __construct(?BaseConnection $db = null)
    {
        $this->db = $db ?? Database::connect();
    }

    /**
     * @return array{
     *   ok: true,
     *   token: string,
     *   username: string,
     *   admin_user_id: int,
     *   group_names: list<string>,
     *   group_ids: list<int>
     * }|array{ok: false, reason: 'skip'|'invalid'|'no_group'|'no_proxy'}
     */
    /**
     * Cari id_pegawai dari identifier login (username pegawai atau NIP).
     */
    public function resolvePegawaiIdFromCredentials(string $username): ?int
    {
        $username = trim($username);
        if ($username === '') {
            return null;
        }

        $p = $this->db->table('pegawai')->select('id_pegawai')->where('username', $username)->get()->getRowArray();
        if ($p !== null && ! empty($p['id_pegawai'])) {
            return (int) $p['id_pegawai'];
        }

        $p = $this->db->table('pegawai')->select('id_pegawai')->where('nip', $username)->get()->getRowArray();
        if ($p !== null && ! empty($p['id_pegawai'])) {
            return (int) $p['id_pegawai'];
        }

        return null;
    }

    /**
     * Pegawai yang di baris `admin_users` punya `id_pegawai` sama + aktif + punya grup Ion
     * → panel memakai RBAC grup (supervisor, HRD, dll.), bukan mode pegawai panel tipis.
     *
     * @return array{admin_user_id: int, username: string, group_names: list<string>}|null
     */
    public function findLinkedAdminForPegawaiId(int $pegawaiId): ?array
    {
        if ($pegawaiId <= 0 || ! $this->db->tableExists('admin_users')) {
            return null;
        }
        if (! $this->db->fieldExists('id_pegawai', 'admin_users')) {
            return null;
        }

        $row = $this->db->table('admin_users')
            ->where('id_pegawai', $pegawaiId)
            ->where('active', 1)
            ->orderBy('id', 'ASC')
            ->get()
            ->getRowArray();

        if ($row === null) {
            return null;
        }

        $userId = (int) ($row['id'] ?? 0);
        if ($userId <= 0) {
            return null;
        }

        $groups = $this->loadAdminGroupsForUser($userId);
        if ($this->db->tableExists('admin_users_groups') && $groups['names'] === []) {
            return null;
        }

        return [
            'admin_user_id' => $userId,
            'username'      => (string) ($row['username'] ?? ''),
            'group_names'   => $groups['names'],
        ];
    }

    public function tryLogin(string $username, string $password): array
    {
        if ($username === '' || $password === '') {
            return ['ok' => false, 'reason' => 'skip'];
        }

        if (! $this->db->tableExists('admin_users')) {
            return ['ok' => false, 'reason' => 'skip'];
        }

        $row = $this->db->table('admin_users')
            ->where('username', $username)
            ->where('active', 1)
            ->get()
            ->getRowArray();

        if ($row === null || empty($row['password'])) {
            return ['ok' => false, 'reason' => 'invalid'];
        }

        $hash = (string) $row['password'];
        if (! $this->verifyAdminPassword($password, $hash)) {
            return ['ok' => false, 'reason' => 'invalid'];
        }

        $userId = (int) $row['id'];
        $groups  = $this->loadAdminGroupsForUser($userId);

        if ($this->db->tableExists('admin_users_groups') && $groups === []) {
            return ['ok' => false, 'reason' => 'no_group'];
        }

        $proxyId = $this->resolveProxyPegawaiId($groups, $row);
        if ($proxyId === null) {
            return ['ok' => false, 'reason' => 'no_proxy'];
        }

        $mobile = new MobileJsonService($this->db);
        $token   = $mobile->issueTokenForPegawaiId($proxyId);
        if ($token === null) {
            return ['ok' => false, 'reason' => 'no_proxy'];
        }

        $this->db->table('admin_users')->where('id', $userId)->update([
            'last_login' => time(),
        ]);

        return [
            'ok'            => true,
            'token'         => $token,
            'username'      => $username,
            'admin_user_id' => $userId,
            'group_names'   => $groups['names'],
            'group_ids'     => $groups['ids'],
        ];
    }

    /**
     * @return array{names: list<string>, ids: list<int>}
     */
    private function loadAdminGroupsForUser(int $userId): array
    {
        if (! $this->db->tableExists('admin_users_groups') || ! $this->db->tableExists('admin_groups')) {
            return ['names' => [], 'ids' => []];
        }

        $rows = $this->db->table('admin_users_groups ug')
            ->select('g.id, g.name')
            ->join('admin_groups g', 'g.id = ug.group_id', 'inner')
            ->where('ug.user_id', $userId)
            ->orderBy('g.id', 'ASC')
            ->get()
            ->getResultArray();

        $names = [];
        $ids   = [];
        foreach ($rows as $r) {
            $ids[]   = (int) ($r['id'] ?? 0);
            $names[] = (string) ($r['name'] ?? '');
        }

        return ['names' => $names, 'ids' => $ids];
    }

    /**
     * Proxy pegawai untuk token API. Bisa diarahkan per grup (mis. HRD) lewat .env.
     *
     * @param array{names: list<string>, ids: list<int>} $groups
     * @param array<string, mixed>|null $adminRow Baris admin_users (untuk id_pegawai per-akun).
     */
    private function resolveProxyPegawaiId(array $groups, ?array $adminRow = null): ?int
    {
        if ($adminRow !== null && $this->db->fieldExists('id_pegawai', 'admin_users')) {
            $assigned = (int) ($adminRow['id_pegawai'] ?? 0);
            if ($assigned > 0 && $this->pegawaiExists($assigned)) {
                return $assigned;
            }
        }

        $fromEnv = env('ADMIN_LOGIN_PROXY_PEGAWAI_ID');
        if (is_string($fromEnv) && $fromEnv !== '') {
            $id = (int) $fromEnv;
            if ($id > 0 && $this->pegawaiExists($id)) {
                return $id;
            }
        }

        $hrdOnly = in_array('hrd', array_map('strtolower', $groups['names']), true)
            && ! in_array('webmaster', array_map('strtolower', $groups['names']), true);
        if ($hrdOnly) {
            $hrdEnv = env('ADMIN_LOGIN_PROXY_PEGAWAI_ID_HRD');
            if (is_string($hrdEnv) && $hrdEnv !== '') {
                $hid = (int) $hrdEnv;
                if ($hid > 0 && $this->pegawaiExists($hid)) {
                    return $hid;
                }
            }
        }

        $super = $this->db->table('pegawai')
            ->select('id_pegawai')
            ->where('super_akses', 'true')
            ->orderBy('id_pegawai', 'ASC')
            ->limit(1)
            ->get()
            ->getRowArray();

        if ($super !== null && ! empty($super['id_pegawai'])) {
            return (int) $super['id_pegawai'];
        }

        $any = $this->db->table('pegawai')
            ->select('id_pegawai')
            ->orderBy('id_pegawai', 'ASC')
            ->limit(1)
            ->get()
            ->getRowArray();

        if ($any !== null && ! empty($any['id_pegawai'])) {
            return (int) $any['id_pegawai'];
        }

        return null;
    }

    private function verifyAdminPassword(string $plain, string $hash): bool
    {
        if (str_starts_with($hash, '$2y$') || str_starts_with($hash, '$2a$') || str_starts_with($hash, '$2b$')) {
            return password_verify($plain, $hash);
        }

        if (strlen($hash) === 32 && ctype_xdigit($hash)) {
            return md5($plain) === $hash;
        }

        return password_verify($plain, $hash);
    }

    private function pegawaiExists(int $id): bool
    {
        return $this->db->table('pegawai')->where('id_pegawai', $id)->countAllResults() > 0;
    }
}
