<?php

namespace App\Jobs;

use App\Models\AdminBatchJob;
use App\Models\Role;
use App\Models\User;
use App\Services\IdentityLimitService;
use App\Services\BinaryPlacementService;
use App\Services\MemberCodeService;
use App\Services\TreeService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class ImportMembersJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(private readonly int $batchId)
    {
    }

    public function handle(IdentityLimitService $identityLimitService, BinaryPlacementService $binaryPlacementService, TreeService $treeService, MemberCodeService $memberCodeService): void
    {
        $batch = AdminBatchJob::query()->find($this->batchId);
        if (! $batch) {
            return;
        }

        $batch->status = 'processing';
        $batch->started_at = now();
        $batch->save();

        $path = Storage::disk('local')->path($batch->file_path);
        if (! file_exists($path)) {
            $batch->status = 'failed';
            $batch->finished_at = now();
            $batch->save();
            return;
        }

        $handle = fopen($path, 'r');
        if (! $handle) {
            $batch->status = 'failed';
            $batch->finished_at = now();
            $batch->save();
            return;
        }

        $headers = fgetcsv($handle);
        if (! $headers) {
            fclose($handle);
            $batch->status = 'failed';
            $batch->finished_at = now();
            $batch->save();
            return;
        }

        $headers = array_map(fn ($h) => strtolower(trim((string) $h)), $headers);
        $rowNumber = 1;
        $errors = [];
        $processed = 0;
        $failed = 0;
        $createdAny = false;
        $ignoreLimits = (bool) data_get($batch->meta, 'ignore_limits', false);

        while (($row = fgetcsv($handle)) !== false) {
            $rowNumber++;
            $data = array_combine($headers, $row) ?: [];
            $name = trim((string) ($data['name'] ?? ''));
            $mobile = trim((string) ($data['mobile'] ?? ''));
            $email = trim((string) ($data['email'] ?? ''));
            $sponsorCode = trim((string) ($data['sponsor_code'] ?? $data['sponsor'] ?? ''));
            $placementLeg = strtolower(trim((string) ($data['placement_leg'] ?? 'auto')));
            $packageAmount = (float) ($data['package_amount'] ?? 0);
            $password = (string) ($data['password'] ?? Str::random(10));

            if ($name === '' || $mobile === '' || $sponsorCode === '') {
                $errors[] = ['row' => $rowNumber, 'error' => 'Missing required fields'];
                $failed++;
                continue;
            }

            $sponsorCode = strtoupper($sponsorCode);
            if (! preg_match('/^LW\d{7}$/i', $sponsorCode)) {
                $errors[] = ['row' => $rowNumber, 'error' => 'Sponsor code format must be LWxxxxxxx'];
                $failed++;
                continue;
            }

            if (! in_array($placementLeg, ['left', 'right', 'auto'], true)) {
                $placementLeg = 'auto';
            }

            try {
                if (! $ignoreLimits) {
                    $identityLimitService->assertCanCreate([
                        'mobile' => $mobile,
                        'email' => $email ?: null,
                    ]);
                }

                $sponsor = User::query()
                    ->where('referral_code', $sponsorCode)
                    ->where('role', 'user')
                    ->first();
                if (! $sponsor) {
                    throw new \RuntimeException('Sponsor not found');
                }


                DB::transaction(function () use ($name, $email, $mobile, $password, $sponsor, $placementLeg, $packageAmount, $binaryPlacementService, $memberCodeService, &$createdAny): void {
                    $lockedSponsor = User::query()
                        ->whereKey($sponsor->id)
                        ->lockForUpdate()
                        ->first();

                    if (! $lockedSponsor || $lockedSponsor->role !== 'user') {
                        throw new \RuntimeException('Sponsor not found');
                    }

                    $slot = $binaryPlacementService->resolveSlot($lockedSponsor->id, $placementLeg);
                    $placementTaken = User::query()
                        ->where('placement_parent_id', (int) $slot['parent_id'])
                        ->where('placement_leg', (string) $slot['position'])
                        ->lockForUpdate()
                        ->exists();
                    if ($placementTaken) {
                        throw new \RuntimeException('Placement leg already occupied');
                    }

                    $user = User::query()->create([
                        'member_code' => $memberCodeService->generateMemberCode(),
                        'referral_code' => $memberCodeService->generateReferralCode(),
                        'name' => $name,
                        'email' => $email !== '' ? $email : null,
                        'mobile' => $mobile,
                        'password' => Hash::make($password),
                        'role' => 'user',
                        'status' => 'pending',
                        'sponsor_id' => $lockedSponsor->id,
                        'placement_parent_id' => (int) $slot['parent_id'],
                        'placement_leg' => (string) $slot['position'],
                        'package_amount' => $packageAmount,
                    ]);

                    DB::table('wallet_balances')->insert(['user_id' => $user->id]);
                    DB::table('user_binary_carry')->insert(['user_id' => $user->id]);

                    $distributorRole = Role::query()->where('code', 'distributor')->first();
                    if ($distributorRole) {
                        $user->roles()->syncWithoutDetaching([$distributorRole->id]);
                    }

                    $createdAny = true;
                });

                $processed++;
            } catch (\Throwable $exception) {
                $errors[] = ['row' => $rowNumber, 'error' => $exception->getMessage()];
                $failed++;
            }
        }

        fclose($handle);

        $errorPath = null;
        if ($errors !== []) {
            $errorPath = 'imports/members/errors-'.$batch->id.'.json';
            Storage::disk('local')->put($errorPath, json_encode($errors, JSON_PRETTY_PRINT));
        }

        $batch->status = $failed > 0 ? 'completed_with_errors' : 'completed';
        $batch->total_rows = $processed + $failed;
        $batch->processed_rows = $processed;
        $batch->failed_rows = $failed;
        $batch->error_report_path = $errorPath;
        $batch->finished_at = now();
        $batch->save();

        if ($createdAny) {
            $treeService->rebuildClosure();
        }
    }
}

