<?php

declare(strict_types=1);

namespace App\Modules\Administration\Services;

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

final class EmployeeTransferService
{
    public function __construct(
        private EmployeeTransferRepository $transfers,
        private EmployeeService $employees
    ) {
    }

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

    public function find(
        int $id
    ): ?array {
        return $this->transfers
            ->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.'
            );
        }

        $transferType = trim(
            (string) (
                $data['transfer_type']
                ?? ''
            )
        );

        if ($transferType === '') {
            throw new RuntimeException(
                'Transfer type is required.'
            );
        }

        $effectiveDate = trim(
            (string) (
                $data['effective_date']
                ?? ''
            )
        );

        if ($effectiveDate === '') {
            throw new RuntimeException(
                'Effective date is required.'
            );
        }

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

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

        $document = [
            'document_file_path' => null,
            'document_original_name' => null,
            'document_mime_type' => null,
            'document_file_size' => null
        ];

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

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

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

            'transfer_type' =>
                $transferType,

            'effective_date' =>
                $effectiveDate,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            'document_file_path' =>
                $document[
                    'document_file_path'
                ],

            'document_original_name' =>
                $document[
                    'document_original_name'
                ],

            'document_mime_type' =>
                $document[
                    'document_mime_type'
                ],

            'document_file_size' =>
                $document[
                    'document_file_size'
                ],

            'status' => 1,

            'created_at' => $now,

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

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

    $transferType = trim(
        (string) (
            $data['transfer_type']
            ?? ''
        )
    );

    if ($transferType === '') {
        throw new RuntimeException(
            'Transfer type is required.'
        );
    }

    $effectiveDate = trim(
        (string) (
            $data['effective_date']
            ?? ''
        )
    );

    if ($effectiveDate === '') {
        throw new RuntimeException(
            'Effective date is required.'
        );
    }

    /*
    |--------------------------------------------------------------------------
    | PRESERVE CURRENT DOCUMENT
    |--------------------------------------------------------------------------
    */

    $document = [
        'document_file_path' =>
            $transfer['document_file_path']
            ?? null,

        'document_original_name' =>
            $transfer['document_original_name']
            ?? null,

        'document_mime_type' =>
            $transfer['document_mime_type']
            ?? null,

        'document_file_size' =>
            $transfer['document_file_size']
            ?? null
    ];


    /*
    |--------------------------------------------------------------------------
    | REPLACE DOCUMENT ONLY WHEN NEW FILE IS PROVIDED
    |--------------------------------------------------------------------------
    */

    if (
        $file !== null
        && (int) (
            $file['error']
            ?? UPLOAD_ERR_NO_FILE
        ) !== UPLOAD_ERR_NO_FILE
    ) {
        $employee =
            $this->employees->find(
                (int) $transfer['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.'
            );
        }

        /*
        | Save new document first.
        */

        $newDocument =
            $this->uploadDocument(
                $file,
                $employeeCode
            );

        /*
        | Remove previous physical file only after
        | the replacement has been saved successfully.
        */

        $oldRelativePath = (string) (
            $transfer['document_file_path']
            ?? ''
        );

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

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

        $document =
            $newDocument;
    }


    /*
    |--------------------------------------------------------------------------
    | UPDATE TRANSFER RECORD
    |--------------------------------------------------------------------------
    */

    $this->transfers->update(
        $id,
        [
            'transfer_type' =>
                $transferType,

            'effective_date' =>
                $effectiveDate,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            'document_file_path' =>
                $document[
                    'document_file_path'
                ],

            'document_original_name' =>
                $document[
                    'document_original_name'
                ],

            'document_mime_type' =>
                $document[
                    'document_mime_type'
                ],

            'document_file_size' =>
                $document[
                    'document_file_size'
                ],

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


    public function toggleStatus(
        int $id
    ): void {
        $transfer =
            $this->transfers->find($id);

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

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

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

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

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

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

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

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

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

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

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

        $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 transfer document 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 files are allowed.'
            );
        }

        $extension =
            $allowedTypes[$mimeType];

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

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

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

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

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

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

        return [
            'document_file_path' =>
                'uploads/employees/'
                . $employeeCode
                . '/transfers/'
                . $fileName,

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

            'document_mime_type' =>
                $mimeType,

            'document_file_size' =>
                $fileSize
        ];
    }

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

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