<?php

declare(strict_types=1);

namespace App\Modules\Administration\Services;

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

final class EmployeeTrainingService
{
    public function __construct(
        private EmployeeTrainingRepository $training,
        private EmployeeService $employees
    ) {
    }

    public function byEmployee(
        int $employeeId
    ): array {
        return $this->training
            ->byEmployee($employeeId);
    }

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

    public function create(
        int $employeeId,
        array $data,
        ?array $file = null
    ): void {
        $employee =
            $this->employees->find(
                $employeeId
            );

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

        $trainingTitle = trim(
            (string) (
                $data['training_title']
                ?? ''
            )
        );

        if ($trainingTitle === '') {
            throw new RuntimeException(
                'Training title is required.'
            );
        }

        $employeeCode = trim(
            (string) (
                $employee['employee_id']
                ?? ''
            )
        );

        if ($employeeCode === '') {
            throw new RuntimeException(
                'Employee ID code is missing.'
            );
        }

        $certificate = [
            'certificate_file_path' =>
                null,

            'certificate_original_name' =>
                null,

            'certificate_mime_type' =>
                null,

            'certificate_file_size' =>
                null
        ];

        if (
            $file !== null
            && (int) (
                $file['error']
                ?? UPLOAD_ERR_NO_FILE
            ) !== UPLOAD_ERR_NO_FILE
        ) {
            $certificate =
                $this->uploadCertificate(
                    $file,
                    $employeeCode
                );
        }

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

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

            'training_title' =>
                $trainingTitle,

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

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

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

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

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

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

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

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

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

            'certificate_file_path' =>
                $certificate[
                    'certificate_file_path'
                ],

            'certificate_original_name' =>
                $certificate[
                    'certificate_original_name'
                ],

            'certificate_mime_type' =>
                $certificate[
                    'certificate_mime_type'
                ],

            'certificate_file_size' =>
                $certificate[
                    'certificate_file_size'
                ],

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

            'status' => 1,

            'created_at' =>
                $now,

            'updated_at' =>
                $now
        ]);
    }
public function update(
    int $id,
    array $data,
    ?array $file = null
): void {
    $training =
        $this->training->find($id);

    if ($training === null) {
        throw new RuntimeException(
            'Training record not found.'
        );
    }

    $trainingTitle = trim(
        (string) (
            $data['training_title']
            ?? ''
        )
    );

    if ($trainingTitle === '') {
        throw new RuntimeException(
            'Training title is required.'
        );
    }

    /*
    |--------------------------------------------------------------------------
    | PRESERVE EXISTING CERTIFICATE
    |--------------------------------------------------------------------------
    */

    $certificate = [
        'certificate_file_path' =>
            $training['certificate_file_path']
            ?? null,

        'certificate_original_name' =>
            $training['certificate_original_name']
            ?? null,

        'certificate_mime_type' =>
            $training['certificate_mime_type']
            ?? null,

        'certificate_file_size' =>
            $training['certificate_file_size']
            ?? null
    ];


    /*
    |--------------------------------------------------------------------------
    | REPLACE CERTIFICATE ONLY IF NEW FILE IS UPLOADED
    |--------------------------------------------------------------------------
    */

    if (
        $file !== null
        && (int) (
            $file['error']
            ?? UPLOAD_ERR_NO_FILE
        ) !== UPLOAD_ERR_NO_FILE
    ) {

        $employee =
            $this->employees->find(
                (int) $training['employee_id']
            );

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

        $employeeCode = trim(
            (string) (
                $employee['employee_id']
                ?? ''
            )
        );

        if ($employeeCode === '') {
            throw new RuntimeException(
                'Employee ID code is missing.'
            );
        }

        $newCertificate =
            $this->uploadCertificate(
                $file,
                $employeeCode
            );

        /*
        |--------------------------------------------------------------------------
        | DELETE OLD FILE AFTER NEW FILE SAVED SUCCESSFULLY
        |--------------------------------------------------------------------------
        */

        $oldRelativePath = (string) (
            $training['certificate_file_path']
            ?? ''
        );

        if ($oldRelativePath !== '') {

            $oldFile =
                PUBLIC_PATH
                . '/'
                . ltrim(
                    $oldRelativePath,
                    '/'
                );

            if (
                is_file($oldFile)
                && is_writable($oldFile)
            ) {
                unlink($oldFile);
            }
        }

        $certificate =
            $newCertificate;
    }


    /*
    |--------------------------------------------------------------------------
    | UPDATE DATABASE
    |--------------------------------------------------------------------------
    */

    $this->training->update(
        $id,
        [
            'training_title' =>
                $trainingTitle,

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

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

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

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

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

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

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

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

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

            'certificate_file_path' =>
                $certificate[
                    'certificate_file_path'
                ],

            'certificate_original_name' =>
                $certificate[
                    'certificate_original_name'
                ],

            'certificate_mime_type' =>
                $certificate[
                    'certificate_mime_type'
                ],

            'certificate_file_size' =>
                $certificate[
                    'certificate_file_size'
                ],

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

            'updated_at' =>
                date('Y-m-d H:i:s')
        ]
    );
}
    public function toggleStatus(
        int $id
    ): void {
        $record =
            $this->training->find($id);

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

        $currentStatus =
            (int) (
                $record['status']
                ?? 0
            );

        $newStatus =
            $currentStatus === 1
                ? 0
                : 1;

        $this->training->updateStatus(
            $id,
            $newStatus
        );
    }

    private function uploadCertificate(
        array $file,
        string $employeeCode
    ): array {
        $uploadError = (int) (
            $file['error']
            ?? UPLOAD_ERR_NO_FILE
        );

        if ($uploadError !== UPLOAD_ERR_OK) {
            throw new RuntimeException(
                'Training certificate upload failed. Error code: '
                . $uploadError
            );
        }

        $fileSize =
            (int) ($file['size'] ?? 0);

        if ($fileSize < 1) {
            throw new RuntimeException(
                'Uploaded certificate is empty.'
            );
        }

        if (
            $fileSize >
            (10 * 1024 * 1024)
        ) {
            throw new RuntimeException(
                'Certificate must not exceed 10 MB.'
            );
        }

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

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

        $mimeType = null;

        /*
        |--------------------------------------------------------------------------
        | METHOD 1: finfo
        |--------------------------------------------------------------------------
        */

        if (class_exists('\finfo')) {
            $fileInfo =
                new \finfo(
                    FILEINFO_MIME_TYPE
                );

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

            if (
                is_string($detectedType)
                && $detectedType !== ''
            ) {
                $mimeType =
                    $detectedType;
            }
        }

        /*
        |--------------------------------------------------------------------------
        | METHOD 2: mime_content_type
        |--------------------------------------------------------------------------
        */

        if (
            $mimeType === null
            && function_exists(
                'mime_content_type'
            )
        ) {
            $detectedType =
                \mime_content_type(
                    $temporaryFile
                );

            if (
                is_string($detectedType)
                && $detectedType !== ''
            ) {
                $mimeType =
                    $detectedType;
            }
        }

        /*
        |--------------------------------------------------------------------------
        | METHOD 3: Browser MIME fallback
        |--------------------------------------------------------------------------
        */

        if ($mimeType === null) {
            $browserMime = trim(
                (string) (
                    $file['type']
                    ?? ''
                )
            );

            if ($browserMime !== '') {
                $mimeType =
                    $browserMime;
            }
        }

        if ($mimeType === null) {
            throw new RuntimeException(
                'Unable to determine certificate file type.'
            );
        }

        $allowedTypes = [
            'application/pdf' =>
                'pdf',

            'image/jpeg' =>
                'jpg',

            'image/png' =>
                'png',

            'application/msword' =>
                'doc',

            'application/vnd.openxmlformats-officedocument.wordprocessingml.document' =>
                'docx'
        ];

        if (
            !isset(
                $allowedTypes[$mimeType]
            )
        ) {
            throw new RuntimeException(
                'Only PDF, JPG, PNG, DOC and DOCX certificate files are allowed.'
            );
        }

        $extension =
            $allowedTypes[$mimeType];

        $directory =
            PUBLIC_PATH
            . '/uploads/employees/'
            . $employeeCode
            . '/training';

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

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

        $fileName =
            $employeeCode
            . '_TRAINING_'
            . date('Ymd_His')
            . '_'
            . bin2hex(
                random_bytes(4)
            )
            . '.'
            . $extension;

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

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

        return [
            'certificate_file_path' =>
                'uploads/employees/'
                . $employeeCode
                . '/training/'
                . $fileName,

            'certificate_original_name' =>
                (string) (
                    $file['name']
                    ?? $fileName
                ),

            'certificate_mime_type' =>
                $mimeType,

            'certificate_file_size' =>
                $fileSize
        ];
    }

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

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