<?php

namespace App\Services\Badges;

use App\Models\BadgePrintLog;
use App\Models\ClassSection;
use App\Models\Role;
use App\Models\TeacherClass;
use App\Models\User;
use App\Models\UserRole;
use FPDF;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;

class BadgeService
{
    public function __construct(
        protected User $userModel,
        protected UserRole $userRoleModel,
        protected Role $roleModel,
        protected TeacherClass $teacherClassModel,
        protected ClassSection $classSectionModel,
        protected BadgePrintLog $badgePrintLogModel
    ) {
    }

    public function generateBadgePdf(
        array $userIds,
        ?string $schoolYear,
        array $rolesMap = [],
        array $classesMap = [],
        ?int $actorId = null
    ): Response {
        $userIds = $this->normalizeIds($userIds);

        $pdf = new FPDF('P', 'mm', 'A4');
        $pdf->SetAutoPageBreak(false);

        $badgeW = 95;
        $badgeH = 67;
        $marginL = 14;
        $marginT = 6;
        $gutterX = 0;
        $gutterY = 0;
        $cols = 2;
        $rows = 4;
        $perPage = $cols * $rows;

        $pagesAdded = 0;
        $i = 0;
        $seen = [];

        foreach ($userIds as $uid) {
            $info = $this->getUserInfoById($uid, $schoolYear);

            if (!$info || !is_array($info)) {
                continue;
            }

            $userId = (int) ($info['user_id'] ?? $uid);
            $name = $this->norm($info['name'] ?? '');

            $postedRole = $rolesMap[$userId] ?? null;
            $roleResolved = $postedRole !== null
                ? $postedRole
                : $this->resolveRole($info);

            $roleResolved = $this->formatRole($roleResolved);
            $info['role_resolved'] = $roleResolved;

            if (!empty($classesMap[$userId])) {
                $info['class_section_name'] = (string) $classesMap[$userId];
            }

            $class = $this->norm($info['class_section_name'] ?? '');

            $badgeKey = strtolower(trim($userId . '|' . $name . '|' . $roleResolved . '|' . $class));
            if (isset($seen[$badgeKey])) {
                continue;
            }
            $seen[$badgeKey] = true;

            if (($i % $perPage) === 0) {
                $pdf->AddPage();
                $pagesAdded++;
            }

            $indexOnPage = $i % $perPage;
            $row = intdiv($indexOnPage, $cols);
            $col = $indexOnPage % $cols;

            $x = $marginL + $col * ($badgeW + $gutterX);
            $y = $marginT + $row * ($badgeH + $gutterY);

            $this->drawBadgeInCell($pdf, $info, $x, $y, $badgeW, $badgeH, $schoolYear);
            $i++;
        }

        if ($pagesAdded === 0) {
            $pdf->AddPage();
            $pdf->SetFont('Arial', '', 10);
            $pdf->SetXY(10, 20);
            $pdf->MultiCell(0, 6, 'No valid staff selected or data not found.', 0, 'L');
        }

        $pdfString = $pdf->Output('S');

        try {
            $this->badgePrintLogModel->logPrints(
                $userIds,
                $actorId,
                $schoolYear,
                is_array($rolesMap) ? $rolesMap : [],
                is_array($classesMap) ? $classesMap : [],
                1
            );
        } catch (\Throwable $e) {
            Log::error('Failed to log badge prints: ' . $e->getMessage());
        }

        return response($pdfString, 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'inline; filename="Staff_Badges.pdf"',
        ]);
    }

    public function getBadgePrintStatus(array $ids, ?string $schoolYear): array
    {
        $ids = $this->normalizeIds($ids);

        return $this->badgePrintLogModel->getStatus($ids, $schoolYear);
    }

    public function logBadgePrint(
        array $ids,
        ?string $schoolYear,
        array $roles = [],
        array $classes = [],
        ?int $actorId = null
    ): int {
        $ids = $this->normalizeIds($ids);

        return $this->badgePrintLogModel->logPrints(
            $ids,
            $actorId,
            $schoolYear,
            is_array($roles) ? $roles : [],
            is_array($classes) ? $classes : [],
            1
        );
    }

    public function getBadgeFormData(?string $schoolYear, array $selectedUserIds = [], ?string $activeRole = null): array
    {
        $selectedUserIds = $this->normalizeIds($selectedUserIds);

        $users = $this->fetchStaffList($schoolYear, $selectedUserIds);

        foreach ($users as &$u) {
            if (!isset($u['user_id'])) {
                if (isset($u['id'])) {
                    $u['user_id'] = (int) $u['id'];
                }
            }

            $rolesStr = $u['roles'] ?? '';
            if ($rolesStr === '' && isset($u['role_name'])) {
                $rolesStr = (string) $u['role_name'];
            }

            $u['roles_raw'] = $rolesStr;
            $u['roles'] = $this->formatRolesCsv($rolesStr);

            if (empty($u['role_name'])) {
                $parts = array_filter(array_map('trim', explode(',', $rolesStr)));
                $u['role_name'] = $parts[0] ?? '';
            }

            $u['role_name_raw'] = $u['role_name'];
            $u['role_name'] = $this->formatRole((string) $u['role_name']);

            $u['class_section_id'] = $u['class_section_id'] ?? null;
            $u['class_section_name'] = $u['class_section_name'] ?? null;
        }
        unset($u);

        foreach ($users as &$u) {
            $rolesDetect = strtolower($u['roles_raw'] ?? ($u['role_name_raw'] ?? ''));
            $isTeacherish = str_contains($rolesDetect, 'teacher') || preg_match('/\bta\b/', $rolesDetect);

            if ($isTeacherish && !empty($u['user_id'])) {
                $assign = $this->getLatestClassForUser((int) $u['user_id'], $schoolYear);
                if ($assign) {
                    $u['class_section_id'] = $assign['class_section_id'] ?? null;
                    $u['class_section_name'] = $assign['class_section_name'] ?? null;
                }
            }
        }
        unset($u);

        $schoolYears = $this->getAvailableSchoolYears();

        $rolesTabs = [
            'teacher' => 'Teachers',
            'ta' => 'Teacher Assistants',
            'admin' => 'Admins',
            'staff' => 'Staff',
        ];

        $activeRole = array_key_exists((string) $activeRole, $rolesTabs) ? (string) $activeRole : 'teacher';

        return [
            'users' => $users,
            'schoolYears' => $schoolYears,
            'selectedYear' => $schoolYear,
            'selectedUserIds' => $selectedUserIds,
            'rolesTabs' => $rolesTabs,
            'active_role' => $activeRole,
        ];
    }

    protected function fetchStaffList(?string $schoolYear, array $selectedUserIds = []): array
    {
        $query = DB::table('users')
            ->leftJoin('user_roles', 'user_roles.user_id', '=', 'users.id')
            ->leftJoin('roles', 'roles.id', '=', 'user_roles.role_id')
            ->select([
                'users.id',
                'users.firstname',
                'users.lastname',
                DB::raw("GROUP_CONCAT(DISTINCT CASE WHEN LOWER(roles.name) != 'parent' THEN roles.name END ORDER BY roles.name SEPARATOR ', ') as roles"),
            ])
            ->groupBy('users.id', 'users.firstname', 'users.lastname');

        if (!empty($selectedUserIds)) {
            $query->whereIn('users.id', $selectedUserIds);
        }

        if (!empty($schoolYear) && Schema::hasColumn('user_roles', 'school_year')) {
            $query->where('user_roles.school_year', $schoolYear);
        }

        return array_map(static fn ($row) => (array) $row, $query->get()->all());
    }

    protected function getLatestClassForUser(int $userId, ?string $schoolYear = null): ?array
    {
        $query = DB::table('teacher_class as tc')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'tc.class_section_id')
            ->select([
                'tc.class_section_id',
                'cs.class_section_name',
            ])
            ->where('tc.teacher_id', $userId);

        if (!empty($schoolYear)) {
            $query->where('tc.school_year', $schoolYear);
        }

        $row = $query
            ->orderByRaw("FIELD(tc.position, 'main', 'ta')")
            ->orderByRaw('IFNULL(tc.updated_at,"0000-00-00 00:00:00") DESC')
            ->orderByDesc('tc.id')
            ->first();

        if (!$row || empty($row->class_section_id)) {
            return null;
        }

        return [
            'class_section_id' => (int) $row->class_section_id,
            'class_section_name' => $row->class_section_name ?? null,
        ];
    }

    public function getUserInfoById(int $id, ?string $schoolYear = null): ?array
    {
        $u = DB::table('users')
            ->select(['id as user_id', 'firstname', 'lastname'])
            ->where('id', $id)
            ->first();

        if (!$u) {
            return null;
        }

        $userId = (int) $u->user_id;
        $fullname = trim(($u->firstname ?? '') . ' ' . ($u->lastname ?? ''));

        $rolesRows = DB::table('user_roles as ur')
            ->join('roles as r', 'r.id', '=', 'ur.role_id')
            ->select(['r.id as role_id', 'r.name as role_name'])
            ->where('ur.user_id', $userId)
            ->get();

        $allRoles = [];
        foreach ($rolesRows as $rr) {
            $name = trim((string) ($rr->role_name ?? ''));
            if ($name !== '' && strtolower($name) !== 'parent') {
                $allRoles[] = ucwords(strtolower($name));
            }
        }

        $allRoles = array_values(array_unique($allRoles));
        $rolesCsv = $allRoles ? implode(', ', $allRoles) : '';

        $tcQuery = DB::table('teacher_class')
            ->select(['class_section_id', 'updated_at', 'id', 'position', 'school_year'])
            ->where('teacher_id', $userId);

        if (!empty($schoolYear)) {
            $tcQuery->where('school_year', $schoolYear);
        }

        $assignment = $tcQuery
            ->orderByRaw('IFNULL(updated_at, "1970-01-01 00:00:00") DESC, id DESC')
            ->first();

        $classId = $assignment->class_section_id ?? null;
        $position = strtolower(trim((string) ($assignment->position ?? '')));
        $className = null;

        if (!empty($classId)) {
            $className = $this->resolveClassName((int) $classId);
        }

        if (in_array($position, ['ta', 'teacher_assistant', 'assistant'], true)) {
            $roleLabel = 'Teacher Assistant';
        } elseif ($position === 'teacher' || $position === 'main') {
            $roleLabel = 'Teacher';
        } elseif (!empty($allRoles)) {
            $roleLabel = $allRoles[0];
        } else {
            $roleLabel = 'Staff';
        }

        $schoolLogo = $this->firstExisting([
            public_path('assets/images/school_logo.png'),
            public_path('assets/images/logo.png'),
        ]);

        $isglLogo = $this->firstExisting([
            public_path('assets/images/isgl_logo.png'),
            public_path('assets/images/isgl.png'),
        ]);

        $jobTitle = '';
        if (!empty($roleLabel) && !empty($className)) {
            $jobTitle = "{$roleLabel} - {$className}";
        } elseif (!empty($roleLabel)) {
            $jobTitle = $roleLabel;
        } elseif (!empty($className)) {
            $jobTitle = $className;
        }

        return [
            'user_id' => $userId,
            'name' => $fullname !== '' ? $fullname : 'STAFF',
            'role' => $roleLabel,
            'roles' => $rolesCsv,
            'class_section_id' => $classId ? (int) $classId : null,
            'class_section_name' => $className,
            'job_title' => $jobTitle,
            'school_name' => 'Al Rahma Sunday School',
            'school_id' => $userId,
            'school_logo' => $schoolLogo,
            'isgl_logo' => $isglLogo,
            'school_year' => $assignment->school_year ?? $schoolYear,
        ];
    }

    protected function drawBadgeInCell(FPDF $pdf, array $data, float $x, float $y, float $w, float $h, ?string $schoolYear): void
    {
        $pdf->SetFillColor(255, 255, 255);
        $pdf->Rect($x, $y, $w, $h, 'F');
        $pdf->SetDrawColor(200, 200, 200);
        $pdf->Rect($x, $y, $w, $h);

        $logoSize = 6;
        if (!empty($data['school_logo']) && file_exists($data['school_logo'])) {
            $pdf->Image($data['school_logo'], $x + $w - 6 - $logoSize, $y + 4, $logoSize + 3, $logoSize - 1);
        }
        if (!empty($data['isgl_logo']) && file_exists($data['isgl_logo'])) {
            $pdf->Image($data['isgl_logo'], $x + 4, $y + 4, $logoSize + 3, $logoSize - 1);
        }

        $padX = 4;
        $cursorY = $y + 4 + $logoSize + 2;

        $schoolName = strtoupper($this->norm($data['school_name'] ?? 'AL RAHMA SUNDAY SCHOOL'));
        $pdf->SetFont('Arial', '', 16);
        $pdf->SetXY($x + $padX, $cursorY);
        $pdf->MultiCell($w - 2 * $padX, 5, $this->toPdf($schoolName), 0, 'C');

        $pdf->Ln(9);
        $pdf->SetFont('Arial', 'B', 14);
        $pdf->SetX($x + $padX);
        $name = strtoupper($this->norm($data['name'] ?? 'STAFF'));
        $pdf->Cell($w - 2 * $padX, 6, $this->toPdf($name), 0, 1, 'C');

        $roleResolved = $this->norm((string) ($data['role_resolved'] ?? ''));
        if ($roleResolved === '') {
            $roleResolved = $this->resolveRole($data);
        }
        if ($roleResolved !== '') {
            $roleResolved = $this->formatRole($roleResolved);
        }

        $classRaw = $this->norm($data['class_section_name'] ?? '');
        $class = $this->formatClass($classRaw);

        $detectSrc = strtolower($this->norm(
            ($data['roles_raw'] ?? '') !== '' ? $data['roles_raw'] : (($data['role_name_raw'] ?? '') !== '' ? $data['role_name_raw'] : $roleResolved)
        ));
        $isTeacherish = str_contains($detectSrc, 'teacher') || preg_match('/\bta\b/', $detectSrc);

        if ($isTeacherish && $class !== '') {
            if (strtolower($class) === 'youth') {
                $display = 'Youth ' . $roleResolved;
            } elseif ($class === 'KG') {
                $display = 'KG ' . $roleResolved;
            } else {
                $display = 'Grade ' . $class . ' ' . $roleResolved;
            }
        } elseif ($roleResolved !== '') {
            $display = $roleResolved;
        } elseif ($class !== '') {
            $display = $class;
        } else {
            $display = 'STAFF';
        }

        $pdf->Ln(6);
        $pdf->SetFont('Arial', '', 14);
        $displayOut = $this->toPdf($display);
        $maxTextWidth = $w - 2 * $padX;
        $best = $this->fitText($pdf, $displayOut, 11, 7, $maxTextWidth);
        $pdf->SetFont('Arial', '', $best + 4);

        if ($pdf->GetStringWidth($displayOut) <= $maxTextWidth) {
            $pdf->SetX($x + $padX);
            $pdf->Cell($maxTextWidth, 5, $displayOut, 0, 1, 'C');
        } else {
            $pdf->SetX($x + $padX);
            $pdf->MultiCell($maxTextWidth, 5, $displayOut, 0, 'C');
        }

        $footerYear = $this->norm((string) ($schoolYear ?? ($data['school_year'] ?? '')));
        if ($footerYear !== '') {
            $pdf->SetFont('Arial', 'I', 14);
            $pdf->SetXY($x + $padX, $y + $h - 9);
            $pdf->Cell($w - 2 * $padX, 4, $this->toPdf($footerYear), 0, 0, 'C');
        }
    }

    protected function resolveRole(array $info): string
    {
        $candidates = [
            'role',
            'active_role',
            'role_name',
            fn ($r) => !empty($r['roles'])
                ? (array_filter(array_map('trim', explode(',', (string) $r['roles'])))[0] ?? '')
                : '',
            'job_title',
            'title',
            'position',
            'staff_role',
            'department_role',
            'dept_role',
        ];

        foreach ($candidates as $key) {
            if (is_callable($key)) {
                $v = $key($info);
                if ($this->norm($v) !== '') {
                    return $this->norm($v);
                }
            } else {
                if (!empty($info[$key])) {
                    $v = $this->norm((string) $info[$key]);
                    if ($v !== '') {
                        return $v;
                    }
                }
            }
        }

        return 'STAFF';
    }

    protected function normalizeIds(array $ids): array
    {
        return array_values(array_unique(array_filter(array_map(static function ($v) {
            if (is_string($v)) {
                $v = trim($v);
            }

            if ($v === '' || $v === null) {
                return null;
            }

            return (int) $v;
        }, $ids), static fn ($v) => $v !== null && $v > 0)));
    }

    protected function norm(?string $s): string
    {
        $s = (string) $s;
        $s = str_replace(["\xC2\xA0", "\xA0"], ' ', $s);
        $s = preg_replace('/\s+/u', ' ', $s) ?? $s;
        $s = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $s) ?? $s;

        return trim($s);
    }

    protected function toPdf(string $s): string
    {
        if (function_exists('iconv')) {
            $o = @iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $s);
            if ($o !== false && $o !== '') {
                return $o;
            }
        }

        if (function_exists('mb_convert_encoding')) {
            $o = @mb_convert_encoding($s, 'ISO-8859-1', 'UTF-8');
            if ($o !== '') {
                return $o;
            }
        }

        $o = @utf8_decode($s);

        return $o !== '' ? $o : 'STAFF';
    }

    protected function fitText(FPDF $pdf, string $text, int $startSize, int $minSize, float $maxWidth): int
    {
        $size = $startSize;
        while ($size >= $minSize) {
            $pdf->SetFontSize($size);
            if ($pdf->GetStringWidth($text) <= $maxWidth) {
                return $size;
            }
            $size--;
        }

        return $minSize;
    }

    protected function formatRole(?string $role): string
    {
        $role = (string) $role;
        $role = str_replace(['-', '_'], ' ', $role);
        $role = preg_replace('/\s+/u', ' ', trim($role));

        if ($role === '') {
            return '';
        }

        $special = [
            'of' => 'of',
            'head' => 'Head',
        ];

        $tokens = explode(' ', $role);
        $out = [];

        foreach ($tokens as $tok) {
            if ($tok === '') {
                continue;
            }

            $core = preg_replace('/^\P{L}+|\P{L}+$/u', '', $tok);
            $start = strpos($tok, $core);

            if ($start === false) {
                $out[] = ucfirst(mb_strtolower($tok, 'UTF-8'));
                continue;
            }

            $pre = substr($tok, 0, $start);
            $suf = substr($tok, $start + strlen($core));
            $lw = mb_strtolower($core, 'UTF-8');

            if (isset($special[$lw])) {
                $coreFmt = $special[$lw];
            } elseif (preg_match('/^\p{L}{1,4}$/u', $core)) {
                $coreFmt = mb_strtoupper($core, 'UTF-8');
            } else {
                $coreFmt = preg_replace_callback(
                    '/\p{L}+/u',
                    static fn ($m) => mb_strtoupper(mb_substr($m[0], 0, 1, 'UTF-8'), 'UTF-8')
                        . mb_strtolower(mb_substr($m[0], 1, null, 'UTF-8'), 'UTF-8'),
                    mb_strtolower($core, 'UTF-8')
                );
            }

            $out[] = $pre . $coreFmt . $suf;
        }

        return implode(' ', $out);
    }

    protected function formatRolesCsv(?string $csv): string
    {
        if ($csv === null) {
            return '';
        }

        $parts = array_filter(array_map('trim', explode(',', (string) $csv)), 'strlen');
        if (empty($parts)) {
            return '';
        }

        return implode(', ', array_map(fn ($r) => $this->formatRole($r), $parts));
    }

    protected function formatClass(string $class): string
    {
        $c = trim($class);
        if ($c === '') {
            return '';
        }

        $lc = strtolower($c);
        if ($lc === 'kg' || $lc === 'kindergarten') {
            return 'KG';
        }
        if ($lc === 'youth') {
            return 'Youth';
        }

        return $c;
    }

    protected function firstExisting(array $paths): ?string
    {
        foreach ($paths as $path) {
            if (!empty($path) && file_exists($path)) {
                return $path;
            }
        }

        return null;
    }

    protected function resolveClassName(int $classSectionId): ?string
    {
        $row = DB::table('classSection')
            ->select('class_section_name')
            ->where('class_section_id', $classSectionId)
            ->first();

        return $row->class_section_name ?? null;
    }

    protected function getAvailableSchoolYears(): array
    {
        $schoolYears = [];

        try {
            if (DB::getSchemaBuilder()->hasTable('teacher_class')) {
                $schoolYears = DB::table('teacher_class')
                    ->selectRaw('DISTINCT school_year')
                    ->whereNotNull('school_year')
                    ->orderByDesc('school_year')
                    ->pluck('school_year')
                    ->filter()
                    ->values()
                    ->all();
            }
        } catch (\Throwable) {
        }

        if (empty($schoolYears)) {
            try {
                if (
                    DB::getSchemaBuilder()->hasTable('user_roles') &&
                    DB::getSchemaBuilder()->hasColumn('user_roles', 'school_year')
                ) {
                    $schoolYears = DB::table('user_roles')
                        ->selectRaw('DISTINCT school_year')
                        ->whereNotNull('school_year')
                        ->orderByDesc('school_year')
                        ->pluck('school_year')
                        ->filter()
                        ->values()
                        ->all();
                }
            } catch (\Throwable) {
            }
        }

        return $schoolYears;
    }
}