<?php

namespace App\Services;

use App\Models\Beneficiary;
use App\Models\Company;
use App\Models\CompanyEmployee;
use App\Models\EmployeeRegistration;
use App\Notifications\EmployeeRegistrationReceivedNotification;
use App\Notifications\EmployeeRegistrationRejectedNotification;
use App\Notifications\EmployeeSubmissionLinkNotification;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;

class EmployeeRegistrationService
{
    public function __construct(
        private readonly RegistrationFileUploadService $fileUploadService,
        private readonly CompanyEmployeeService $companyEmployeeService,
        private readonly NinEncryptionService $ninEncryptionService,
        private readonly PortalUserService $portalUserService
    ) {}

    /**
     * Submit a new employee registration from public endpoint.
     */
    public function submitPublicRegistrationForm(array $data): EmployeeRegistration
    {
        try {
            return DB::transaction(function () use ($data) {
                // Upload files
                $filePaths = $this->uploadKycFiles($data);

                // Prepare identity card number fields based on type
                $identityCardFields = $this->prepareIdentityCardNumberFields($data);

                // Create Employee registration
                $submission = EmployeeRegistration::create([
                    'company_id' => $data['company_id'],
                    'submission_type' => 'public_form',
                    'status' => EmployeeRegistration::STATUS_PENDING,
                    // BIO-DATA INFORMATION
                    'title' => $data['title'] ?? null,
                    'first_name' => $data['first_name'],
                    'last_name' => $data['last_name'],
                    'middle_name' => $data['middle_name'] ?? null,
                    'gender' => $data['gender'] ?? null,
                    'date_of_birth' => $data['date_of_birth'],
                    'nationality' => $data['nationality'],
                    'phone_number' => $data['phone_number'],
                    'confirm_phone_number' => $data['confirm_phone_number'],
                    'foreign_number' => $data['foreign_number'] ?? null,
                    'email_address' => $data['email_address'],
                    'confirm_email_address' => $data['confirm_email_address'],
                    'country' => $data['country'],
                    'state' => $data['state'],
                    'city' => $data['city'],
                    'chn' => $data['chn'] ?? null,
                    'house_address' => $data['house_address'],
                    'previous_address' => $data['previous_address'] ?? null,
                    // BANK INFORMATION
                    // Decrypt RSA-encrypted BVN, re-encrypt with AES-256-GCM, store packed
                    'bvn_encrypted' => $this->reEncryptWithAes($data['encrypted_bvn']),
                    'bank_name' => $data['bank_name'] ?? null,
                    'bank_account_number' => $data['bank_account_number'] ?? null,
                    'tin_or_pin' => $data['tin_or_pin'] ?? null,
                    'bank_details_consent' => $data['bank_details_consent'] ?? false,
                    // IDENTITY INFORMATION
                    'identity_card_type' => $data['identity_card_type'],
                    ...$identityCardFields,
                    // File paths
                    'identity_card_path' => $filePaths['identity_card'],
                    'scanned_signature_path' => $filePaths['scanned_signature'],
                    'passport_photograph_path' => $filePaths['passport_photograph'],
                    'nin_document_path' => $filePaths['nin_document'] ?? null,
                    // One-go: store NIN + beneficiaries temporarily if submitted upfront
                    'encrypted_nin_temp' => $data['encrypted_nin'] ?? null,
                    'beneficiaries_data' => ! empty($data['beneficiaries']) ? $data['beneficiaries'] : null,
                    // Consent
                    'consent_given' => true,
                ]);

                // Send confirmation email
                try {
                    Notification::route('mail', $submission->email_address)
                        ->notify(new EmployeeRegistrationReceivedNotification($submission));
                } catch (\Exception $e) {
                    Log::error('Failed to send Employee registration confirmation email: ' . $e->getMessage(), [
                        'submission_id' => $submission->id,
                        'exception' => $e,
                    ]);
                }

                return $submission;
            });
        } catch (\Exception $e) {
            Log::error('Error submitting public employee registration: ' . $e->getMessage(), [
                'exception' => $e,
                'data' => $data,
            ]);
            throw $e;
        }
    }

    /**
     * Approve an Employee registration.
     * Creates CompanyEmployee and PortalUser accounts WITHOUT sending invitation.
     */
    public function approveSubmission(EmployeeRegistration $submission, int $approvedBy): EmployeeRegistration
    {
        try {
            return DB::transaction(function () use ($submission, $approvedBy) {
                // Create company employee account (without sending invitation)
                $employee = $this->companyEmployeeService->createEmployeeWithoutInvite([
                    'first_name' => $submission->first_name,
                    'last_name' => $submission->last_name,
                    'email' => $submission->email_address,
                    'date_of_birth' => $submission->date_of_birth,
                    'phone' => $submission->phone_number,
                ], $submission->company_id);

                // One-go path: NIN + beneficiaries were submitted at registration time
                $hasNin = ! empty($submission->encrypted_nin_temp);
                $hasBeneficiaries = ! empty($submission->beneficiaries_data);

                if ($hasNin) {
                    try {
                        $decryptedNin = $this->ninEncryptionService->decryptWithPrivateKey($submission->encrypted_nin_temp);
                        $aesEncrypted = $this->ninEncryptionService->encrypt($decryptedNin);
                        $employee->update([
                            'nin_encrypted' => $aesEncrypted['iv'] . ':' . $aesEncrypted['ciphertext'] . ':' . $aesEncrypted['tag'],
                        ]);
                    } catch (\Exception $e) {
                        Log::warning('Failed to transfer NIN from registration to employee record on approval', [
                            'submission_id' => $submission->id,
                            'employee_id' => $employee->id,
                            'error' => $e->getMessage(),
                        ]);
                        // Continue approval — admin can resend submission link for NIN
                        $hasNin = false;
                    }
                }

                if ($hasBeneficiaries) {
                    foreach ($submission->beneficiaries_data as $ben) {
                        Beneficiary::create([
                            'company_employee_id' => $employee->id,
                            'first_name' => $ben['first_name'],
                            'last_name' => $ben['last_name'],
                            'date_of_birth' => $ben['date_of_birth'],
                            'address' => $ben['address'],
                            'percentage_allocation' => $ben['percentage_allocation'],
                        ]);
                    }
                }

                // If both NIN and beneficiaries were provided, mark account as active immediately
                $accountStatus = ($hasNin && $hasBeneficiaries)
                    ? EmployeeRegistration::ACCOUNT_STATUS_ACTIVE
                    : EmployeeRegistration::ACCOUNT_STATUS_PENDING;

                // Update submission status to approved
                $submission->update([
                    'status' => EmployeeRegistration::STATUS_APPROVED,
                    'company_employee_id' => $employee->id,
                    'approved_by' => $approvedBy,
                    'approved_at' => now(),
                    'account_status' => $accountStatus,
                ]);

                Log::info('Employee registration approved and account created', [
                    'submission_id' => $submission->id,
                    'employee_id' => $employee->id,
                    'approved_by' => $approvedBy,
                ]);

                return $submission->fresh();
            });
        } catch (\Exception $e) {
            Log::error('Error approving Employee registration: ' . $e->getMessage(), [
                'submission_id' => $submission->id,
                'exception' => $e,
            ]);
            throw $e;
        }
    }

    /**
     * Send a tokenised submission link to an employee.
     * Approves the registration, creates the employee record (no PortalUser),
     * generates a one-time submission token, and emails the link.
     *
     * @param EmployeeRegistration $submission
     * @param string|null $email Optional override email. Defaults to the registered email.
     * @param int|null $approvedBy Admin user ID.
     */
    public function sendInvitation(EmployeeRegistration $submission, ?string $email = null, ?int $approvedBy = null): CompanyEmployee
    {
        try {
            if ($submission->status === 'rejected') {
                throw new \Exception('Cannot send invitation to a rejected registration');
            }

            if ($submission->account_status === 'active') {
                throw new \Exception('Employee has already submitted their details. Cannot resend.');
            }

            $sendTo = $email ?? $submission->email_address;

            $employee = DB::transaction(function () use ($submission, $approvedBy) {
                // Approve the registration if not already approved
                if ($submission->status !== 'approved') {
                    $submission->update([
                        'status' => 'approved',
                        'approved_by' => $approvedBy,
                        'approved_at' => now(),
                    ]);
                }

                // Resend path — employee record already exists, just regenerate the token
                if ($submission->account_status === 'invited' && $submission->company_employee_id) {
                    $employee = CompanyEmployee::find($submission->company_employee_id);

                    if (! $employee) {
                        throw new \Exception('Employee record not found for resending link');
                    }

                    // Generate a fresh token (clears old one)
                    $employee->generateSubmissionToken();

                    Log::info('Employee submission link regenerated', [
                        'submission_id' => $submission->id,
                        'employee_id' => $employee->id,
                    ]);

                    return $employee->fresh(['company']);
                }

                // First send — create employee record only (no PortalUser)
                $employee = $this->companyEmployeeService->createEmployeeRecord([
                    'first_name' => $submission->first_name,
                    'last_name' => $submission->last_name,
                    'email' => $submission->email_address,
                    'date_of_birth' => $submission->date_of_birth,
                    'phone' => $submission->phone_number,
                ], $submission->company_id);

                // Generate the one-time submission token
                $employee->generateSubmissionToken();

                $submission->update([
                    'company_employee_id' => $employee->id,
                    'account_status' => 'invited',
                ]);

                Log::info('Employee registration approved and submission link sent', [
                    'submission_id' => $submission->id,
                    'employee_id' => $employee->id,
                    'approved_by' => $approvedBy,
                ]);

                return $employee->fresh(['company']);
            });

            // Send email after transaction commits
            try {
                Notification::route('mail', $sendTo)->notify(new EmployeeSubmissionLinkNotification(
                    token: $employee->submission_token,
                    employeeName: $employee->full_name,
                    companyName: $employee->company->name ?? 'your company',
                ));
            } catch (\Exception $e) {
                Log::error('Failed to send employee submission link email: ' . $e->getMessage(), [
                    'employee_id' => $employee->id,
                    'send_to' => $sendTo,
                ]);
                // Don't fail — link can be resent by admin
            }

            return $employee;
        } catch (\Exception $e) {
            Log::error('Error sending Employee registration submission link: ' . $e->getMessage(), [
                'submission_id' => $submission->id,
                'exception' => $e,
            ]);
            throw $e;
        }
    }

    /**
     * Verify an employee registration (mark as verified).
     * This is a dummy implementation for now.
     */
    public function verifyRegistration(EmployeeRegistration $submission, int $verifiedBy): EmployeeRegistration
    {
        try {
            return DB::transaction(function () use ($submission, $verifiedBy) {
                // Ensure registration is approved before verification
                if ($submission->status !== EmployeeRegistration::STATUS_APPROVED) {
                    throw new \Exception('Only approved registrations can be verified');
                }

                // Update submission status to verified
                $submission->update([
                    'status' => EmployeeRegistration::STATUS_VERIFIED,
                ]);

                Log::info('Employee registration verified', [
                    'submission_id' => $submission->id,
                    'verified_by' => $verifiedBy,
                ]);

                return $submission->fresh();
            });
        } catch (\Exception $e) {
            Log::error('Error verifying Employee registration: ' . $e->getMessage(), [
                'submission_id' => $submission->id,
                'exception' => $e,
            ]);
            throw $e;
        }
    }

    /**
     * Reject an Employee registration.
     */
    public function rejectSubmission(EmployeeRegistration $submission, int $rejectedBy, ?string $reason = null): EmployeeRegistration
    {
        try {
            return DB::transaction(function () use ($submission, $rejectedBy, $reason) {
                // Update submission status
                $submission->update([
                    'status' => 'rejected',
                    'rejection_reason' => $reason,
                    'rejected_by' => $rejectedBy,
                    'rejected_at' => now(),
                ]);

                // Send rejection notification
                try {
                    Notification::route('mail', $submission->email_address)
                        ->notify(new EmployeeRegistrationRejectedNotification($submission));
                } catch (\Exception $e) {
                    Log::error('Failed to send Employee KYC rejection email: ' . $e->getMessage(), [
                        'submission_id' => $submission->id,
                        'exception' => $e,
                    ]);
                }

                Log::info('Employee registration rejected', [
                    'submission_id' => $submission->id,
                    'rejected_by' => $rejectedBy,
                    'reason' => $reason,
                ]);

                return $submission->fresh();
            });
        } catch (\Exception $e) {
            Log::error('Error rejecting Employee registration: ' . $e->getMessage(), [
                'submission_id' => $submission->id,
                'exception' => $e,
            ]);
            throw $e;
        }
    }

    /**
     * Get all pending Employee registrations.
     */
    public function getPendingSubmissions(int $perPage = 15)
    {
        return EmployeeRegistration::pending()
            ->with('company')
            ->orderBy('created_at', 'desc')
            ->paginate($perPage);
    }

    /**
     * Get all Employee registrations with optional filters.
     */
    public function getAllSubmissions(array $filters = [], int $perPage = 15)
    {
        $query = EmployeeRegistration::query()->with('company');

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        if (isset($filters['submission_type'])) {
            $query->where('submission_type', $filters['submission_type']);
        }

        if (isset($filters['company_id'])) {
            $query->where('company_id', $filters['company_id']);
        }

        if (isset($filters['from_date'])) {
            $query->whereDate('created_at', '>=', $filters['from_date']);
        }

        if (isset($filters['to_date'])) {
            $query->whereDate('created_at', '<=', $filters['to_date']);
        }

        return $query->orderBy('created_at', 'desc')->paginate($perPage);
    }

    /**
     * Get all Employee registrations as a lazy collection for export.
     */
    public function getSubmissionsForExport(array $filters = [])
    {
        $query = EmployeeRegistration::query()->with('company');

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }
        if (isset($filters['submission_type'])) {
            $query->where('submission_type', $filters['submission_type']);
        }
        if (isset($filters['company_id'])) {
            $query->where('company_id', $filters['company_id']);
        }
        if (isset($filters['from_date'])) {
            $query->whereDate('created_at', '>=', $filters['from_date']);
        }
        if (isset($filters['to_date'])) {
            $query->whereDate('created_at', '<=', $filters['to_date']);
        }

        return $query->orderBy('created_at', 'desc')->cursor();
    }

    /**
     * Get a specific submission with relationships.
     */
    public function getSubmission(EmployeeRegistration $submission): EmployeeRegistration
    {
        return $submission->load(['company', 'companyEmployee', 'approvedBy', 'rejectedBy']);
    }

    /**
     * Update an Employee registration.
     */
    public function updateSubmission(EmployeeRegistration $submission, array $data): EmployeeRegistration
    {
        try {
            return DB::transaction(function () use ($submission, $data) {
                // Only allow updates for pending or approved submissions
                if ($submission->isRejected()) {
                    throw new \Exception('Cannot update a rejected submission.');
                }

                $submission->update($data);

                Log::info('Employee registration updated', [
                    'submission_id' => $submission->id,
                    'updated_fields' => array_keys($data),
                ]);

                return $submission->fresh();
            });
        } catch (\Exception $e) {
            Log::error('Error updating Employee registration: ' . $e->getMessage(), [
                'submission_id' => $submission->id,
                'exception' => $e,
            ]);
            throw $e;
        }
    }

    /**
     * Upload all KYC files.
     */
    private function uploadKycFiles(array $data): array
    {
        $filePaths = [];

        if (isset($data['identity_card'])) {
            $filePaths['identity_card'] = $this->fileUploadService->uploadDocument(
                $data['identity_card'],
                'identity_card'
            );
        }

        if (isset($data['scanned_signature'])) {
            $filePaths['scanned_signature'] = $this->fileUploadService->uploadDocument(
                $data['scanned_signature'],
                'scanned_signature'
            );
        }

        if (isset($data['passport_photograph'])) {
            $filePaths['passport_photograph'] = $this->fileUploadService->uploadDocument(
                $data['passport_photograph'],
                'passport_photograph'
            );
        }

        if (isset($data['nin_document'])) {
            $filePaths['nin_document'] = $this->fileUploadService->uploadDocument(
                $data['nin_document'],
                'nin_document'
            );
        }

        return $filePaths;
    }

    /**
     * Prepare identity card number fields based on card type.
     *
     * @param array $data
     * @return array Fields to be stored in database
     */
    private function prepareIdentityCardNumberFields(array $data): array
    {
        if ($data['identity_card_type'] === 'National Identity Number') {
            return [
                'identity_card_number' => null,
                'nin_number_encrypted' => $this->reEncryptWithAes($data['encrypted_nin']),
            ];
        } else {
            return [
                'identity_card_number' => $data['identity_card_number'] ?? null,
                'nin_number_encrypted' => null,
            ];
        }
    }

    /**
     * RSA-decrypt a frontend-encrypted value, re-encrypt with AES-256-GCM,
     * and return the packed "iv:ciphertext:tag" string for storage.
     * In the testing environment, returns a placeholder packed string.
     */
    private function reEncryptWithAes(string $rsaEncrypted): string
    {
        if (app()->environment('testing')) {
            return 'dGVzdA==:dGVzdA==:dGVzdA==';
        }

        $plaintext = $this->ninEncryptionService->decryptWithPrivateKey($rsaEncrypted);
        $aes = $this->ninEncryptionService->encrypt($plaintext);

        return $aes['iv'] . ':' . $aes['ciphertext'] . ':' . $aes['tag'];
    }
}
