<?php

declare(strict_types=1);

namespace App\Modules\Administration\Services;

use App\Modules\Administration\Repositories\EmployeeRepository;
use RuntimeException;

final class EmployeeService
{
    public function __construct(
        private EmployeeRepository $employees
    ) {
    }

    public function all(): array
    {
        return $this->employees->all();
    }

    public function find(
        int $id
    ): ?array {
        return $this->employees->find($id);
    }

    public function profile(
        int $id
    ): ?array {
        return $this->employees
            ->findDetailed($id);
    }

    public function formData(): array
    {
        return [
            'ranks' =>
                $this->employees->ranks(),

            'departments' =>
                $this->employees->departments(),

            'positions' =>
                $this->employees->positions(),

            'states' =>
                $this->employees->states(),

            'counties' =>
                $this->employees->counties(),

            'payams' =>
                $this->employees->payams()
        ];
    }

    public function create(
        array $data,
        ?array $photo = null
    ): void {
        $firstName = trim(
            (string) (
                $data['first_name'] ?? ''
            )
        );

        $middleName = trim(
            (string) (
                $data['middle_name'] ?? ''
            )
        );

        $lastName = trim(
            (string) (
                $data['last_name'] ?? ''
            )
        );

        $gender = strtoupper(
            trim(
                (string) (
                    $data['gender'] ?? ''
                )
            )
        );

        if ($firstName === '') {
            throw new RuntimeException(
                'First name is required.'
            );
        }

        if ($lastName === '') {
            throw new RuntimeException(
                'Last name is required.'
            );
        }

        if (
            !in_array(
                $gender,
                [
                    'MALE',
                    'FEMALE',
                    'OTHER'
                ],
                true
            )
        ) {
            throw new RuntimeException(
                'Valid gender is required.'
            );
        }

        $employeeId =
            $this->generateEmployeeId();

$photoPath = null;

if (
    $photo !== null
    && isset($photo['error'])
    && (int) $photo['error']
        !== UPLOAD_ERR_NO_FILE
) {
    $photoPath = $this->uploadPhoto(
        $photo,
        $employeeId
    );
}

        $this->employees->create([
            'employee_id' =>
                $employeeId,

            'first_name' =>
                $firstName,

            'middle_name' =>
                $middleName !== ''
                    ? $middleName
                    : null,

            'last_name' =>
                $lastName,

            'gender' =>
                $gender,

            'date_of_birth' =>
                $this->nullableDate(
                    $data['date_of_birth']
                    ?? null
                ),

            'nationality_number' =>
    $this->nullableString(
        $data[
            'nationality_number'
        ] ?? null
    ),

'police_id_number' =>
    $this->nullableString(
        $data[
            'police_id_number'
        ] ?? null
    ),

'rank_id' =>
    $this->nullableId(
        $data['rank_id'] ?? null
    ),
            'rank_id' =>
                $this->nullableId(
                    $data['rank_id'] ?? null
                ),

            'department_id' =>
                $this->nullableId(
                    $data['department_id']
                    ?? null
                ),

            'position_id' =>
                $this->nullableId(
                    $data['position_id']
                    ?? null
                ),

            'state_id' =>
                $this->nullableId(
                    $data['state_id']
                    ?? null
                ),

            'county_id' =>
                $this->nullableId(
                    $data['county_id']
                    ?? null
                ),

            'payam_id' =>
                $this->nullableId(
                    $data['payam_id']
                    ?? null
                ),

            'hire_date' =>
                $this->nullableDate(
                    $data['hire_date']
                    ?? null
                ),

            'employment_status' =>
                'ACTIVE',

            'photo' =>
                $photoPath,

            'biometric_status' =>
                'NOT_REGISTERED',

            'created_by' =>
                $this->currentUserId(),

            'updated_by' =>
                $this->currentUserId(),

            'status' =>
                1,

            'created_at' =>
                date('Y-m-d H:i:s'),

            'updated_at' =>
                date('Y-m-d H:i:s')
        ]);
    }

    public function update(
        int $id,
        array $data,
        ?array $photo = null
    ): void {
        $employee =
            $this->employees->find($id);

        if ($employee === null) {
            throw new RuntimeException(
                'Employee not found.'
            );
        }

        $firstName = trim(
            (string) (
                $data['first_name'] ?? ''
            )
        );

        $middleName = trim(
            (string) (
                $data['middle_name'] ?? ''
            )
        );

        $lastName = trim(
            (string) (
                $data['last_name'] ?? ''
            )
        );

        $gender = strtoupper(
            trim(
                (string) (
                    $data['gender'] ?? ''
                )
            )
        );

        if (
            $firstName === ''
            || $lastName === ''
        ) {
            throw new RuntimeException(
                'First name and last name are required.'
            );
        }

        if (
            !in_array(
                $gender,
                [
                    'MALE',
                    'FEMALE',
                    'OTHER'
                ],
                true
            )
        ) {
            throw new RuntimeException(
                'Invalid gender.'
            );
        }

$photoPath =
    $employee['photo'] ?? null;

if (
    $photo !== null
    && isset($photo['error'])
    && (int) $photo['error']
        !== UPLOAD_ERR_NO_FILE
) {
    $photoPath = $this->uploadPhoto(
        $photo,
        (string) $employee['employee_id']
    );
}

        $this->employees->update(
            $id,
            [
                'first_name' =>
                    $firstName,

                'middle_name' =>
                    $middleName !== ''
                        ? $middleName
                        : null,

                'last_name' =>
                    $lastName,

                'gender' =>
                    $gender,

                'date_of_birth' =>
                    $this->nullableDate(
                        $data['date_of_birth']
                        ?? null
                    ),

'nationality_number' =>
    $this->nullableString(
        $data[
            'nationality_number'
        ] ?? null
    ),

'police_id_number' =>
    $this->nullableString(
        $data[
            'police_id_number'
        ] ?? null
    ),

'rank_id' =>
    $this->nullableId(
        $data['rank_id'] ?? null
    ),

                'department_id' =>
                    $this->nullableId(
                        $data['department_id']
                        ?? null
                    ),

                'position_id' =>
                    $this->nullableId(
                        $data['position_id']
                        ?? null
                    ),

                'state_id' =>
                    $this->nullableId(
                        $data['state_id']
                        ?? null
                    ),

                'county_id' =>
                    $this->nullableId(
                        $data['county_id']
                        ?? null
                    ),

                'payam_id' =>
                    $this->nullableId(
                        $data['payam_id']
                        ?? null
                    ),

                'hire_date' =>
                    $this->nullableDate(
                        $data['hire_date']
                        ?? null
                    ),

                'employment_status' =>
                    $this->validateEmploymentStatus(
                        (string) (
                            $data[
                                'employment_status'
                            ]
                            ?? 'ACTIVE'
                        )
                    ),

                'photo' =>
                    $photoPath,

                'status' =>
                    isset($data['status'])
                        ? 1
                        : 0,

                'updated_by' =>
                    $this->currentUserId(),

                'updated_at' =>
                    date('Y-m-d H:i:s')
            ]
        );
    }

    private function generateEmployeeId(): string
    {
        $last =
            $this->employees->lastEmployee();

        $nextNumber = 1;

        if ($last !== null) {
            $lastId = (string) (
                $last['employee_id'] ?? ''
            );

            if (
                preg_match(
                    '/SSNPS-(\d+)/',
                    $lastId,
                    $matches
                )
            ) {
                $nextNumber =
                    ((int) $matches[1]) + 1;
            } else {
                $nextNumber =
                    ((int) $last['id']) + 1;
            }
        }

        return sprintf(
            'SSNPS-%06d',
            $nextNumber
        );
    }

private function uploadPhoto(
    array $photo,
    string $employeeId
): string {
    if (
        (int) ($photo['error'] ?? 1)
        !== UPLOAD_ERR_OK
    ) {
        throw new RuntimeException(
            'Employee photo upload failed.'
        );
    }

    $maxSize = 5 * 1024 * 1024;

    if (
        (int) ($photo['size'] ?? 0)
        > $maxSize
    ) {
        throw new RuntimeException(
            'Photo must not exceed 5 MB.'
        );
    }

    $temporaryFile =
        (string) ($photo['tmp_name'] ?? '');

    if (
        $temporaryFile === ''
        || !is_uploaded_file($temporaryFile)
    ) {
        throw new RuntimeException(
            'Invalid uploaded photo.'
        );
    }

    $mimeType = null;

    if (class_exists('\finfo')) {

        $fileInfo = new \finfo(
            FILEINFO_MIME_TYPE
        );

        $detectedType =
            $fileInfo->file(
                $temporaryFile
            );

        if (is_string($detectedType)) {
            $mimeType = $detectedType;
        }
    }

    if ($mimeType === null) {

        $imageInfo =
            @getimagesize(
                $temporaryFile
            );

        if (
            $imageInfo !== false
            && isset($imageInfo['mime'])
        ) {
            $mimeType =
                (string) $imageInfo['mime'];
        }
    }

    $allowedTypes = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'image/webp' => 'webp'
    ];

    if (
        $mimeType === null
        || !isset(
            $allowedTypes[$mimeType]
        )
    ) {
        throw new RuntimeException(
            'Only JPG, PNG and WEBP photos are allowed.'
        );
    }

    $extension =
        $allowedTypes[$mimeType];

    /*
    |--------------------------------------------------------------------------
    | Employee Storage Root
    |--------------------------------------------------------------------------
    */

    $baseDirectory =
        PUBLIC_PATH
        . '/uploads/employees/'
        . $employeeId;

    /*
    |--------------------------------------------------------------------------
    | Employee Folder Structure
    |--------------------------------------------------------------------------
    */

    $directories = [
        $baseDirectory,
        $baseDirectory . '/photo',
        $baseDirectory . '/documents',
        $baseDirectory . '/attachments'
    ];

    foreach ($directories as $directory) {

        if (
            !is_dir($directory)
            && !mkdir(
                $directory,
                0755,
                true
            )
            && !is_dir($directory)
        ) {
            throw new RuntimeException(
                'Unable to create employee directory.'
            );
        }
    }

    $photoDirectory =
        $baseDirectory
        . '/photo';

    if (!is_writable($photoDirectory)) {
        throw new RuntimeException(
            'Employee photo directory is not writable.'
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Create Secure Random Filename
    |--------------------------------------------------------------------------
    */

$fileName =
    $employeeId
    . '_PHOTO_'
    . date('Ymd_His')
    . '.'
    . $extension;

    $destination =
        $photoDirectory
        . '/'
        . $fileName;

    if (
        !move_uploaded_file(
            $temporaryFile,
            $destination
        )
    ) {
        throw new RuntimeException(
            'Unable to save employee photo.'
        );
    }

    /*
    |--------------------------------------------------------------------------
    | Return Relative Database Path
    |--------------------------------------------------------------------------
    */

    return 'uploads/employees/'
        . $employeeId
        . '/photo/'
        . $fileName;
}

    private function nullableId(
        mixed $value
    ): ?int {
        $id = (int) $value;

        return $id > 0
            ? $id
            : null;
    }

    private function nullableString(
        mixed $value
    ): ?string {
        $value = trim(
            (string) $value
        );

        return $value !== ''
            ? $value
            : null;
    }

    private function nullableDate(
        mixed $value
    ): ?string {
        $value = trim(
            (string) $value
        );

        return $value !== ''
            ? $value
            : null;
    }

    private function currentUserId(): ?int
    {
        $userId = (int) (
            $_SESSION['user_id'] ?? 0
        );

        return $userId > 0
            ? $userId
            : null;
    }

    private function validateEmploymentStatus(
        string $status
    ): string {
        $status = strtoupper(
            trim($status)
        );

        $allowed = [
            'ACTIVE',
            'INACTIVE',
            'RETIRED',
            'SUSPENDED',
            'DECEASED'
        ];

        if (
            !in_array(
                $status,
                $allowed,
                true
            )
        ) {
            throw new RuntimeException(
                'Invalid employment status.'
            );
        }

        return $status;
    }
}