<?php

namespace App\Services;

use App\Models\Category;
use App\Models\Plan;
use App\Models\PlanVendorMapping;
use App\Models\Vendor;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;

class BulkPlanCodeImportService
{
    /**
     * @return array{
     *   scope: array{category_id:int,category_slug:string,network_id:int|null},
     *   total_rows:int,
     *   processed_rows:int,
     *   created:int,
     *   updated:int,
     *   skipped:int,
     *   error_count:int,
     *   errors:array<int,array{row:int,message:string}>
     * }
     */
    public function importForScope(
        Category $category,
        ?int $networkId,
        UploadedFile $csvFile,
        ?int $defaultVendorId = null
    ): array {
        $report = [
            'scope' => [
                'category_id' => (int) $category->id,
                'category_slug' => (string) $category->slug,
                'network_id' => $networkId,
            ],
            'total_rows' => 0,
            'processed_rows' => 0,
            'created' => 0,
            'updated' => 0,
            'skipped' => 0,
            'error_count' => 0,
            'errors' => [],
        ];

        if (! $this->supportsPlanCodeImport($category->slug)) {
            $report['errors'][] = [
                'row' => 0,
                'message' => 'Selected category does not use vendor plan codes for import.',
            ];
            $report['error_count'] = 1;

            return $report;
        }

        if (! $csvFile->isValid() || ! $csvFile->getRealPath()) {
            $report['errors'][] = [
                'row' => 0,
                'message' => 'Uploaded CSV file is invalid.',
            ];
            $report['error_count'] = 1;

            return $report;
        }

        $handle = fopen($csvFile->getRealPath(), 'r');
        if (! $handle) {
            $report['errors'][] = [
                'row' => 0,
                'message' => 'Could not read uploaded CSV file.',
            ];
            $report['error_count'] = 1;

            return $report;
        }

        try {
            $header = fgetcsv($handle);
            if (! is_array($header) || count($header) === 0) {
                $report['errors'][] = [
                    'row' => 0,
                    'message' => 'CSV header row is missing.',
                ];
                $report['error_count'] = 1;

                return $report;
            }

            $columnMap = $this->buildColumnMap($header);
            $missing = $this->missingRequiredColumns($columnMap, $defaultVendorId);
            if (! empty($missing)) {
                $report['errors'][] = [
                    'row' => 0,
                    'message' => 'CSV is missing required column(s): '.implode(', ', $missing).'.',
                ];
                $report['error_count'] = 1;

                return $report;
            }

            $rowNumber = 1;
            while (($row = fgetcsv($handle)) !== false) {
                $rowNumber++;

                if ($this->isBlankRow($row)) {
                    continue;
                }

                $report['total_rows']++;
                $payload = $this->extractRowPayload($row, $columnMap);
                $result = $this->importSingleRow(
                    category: $category,
                    networkId: $networkId,
                    row: $payload,
                    defaultVendorId: $defaultVendorId
                );

                if ($result['status'] === 'error') {
                    $report['errors'][] = [
                        'row' => $rowNumber,
                        'message' => $result['message'],
                    ];
                    continue;
                }

                $report['processed_rows']++;
                $report[$result['status']]++;
            }
        } finally {
            fclose($handle);
        }

        $report['error_count'] = count($report['errors']);

        return $report;
    }

    /**
     * @param array<string,mixed> $row
     * @return array{status:'created'|'updated'|'skipped'|'error',message:string}
     */
    protected function importSingleRow(
        Category $category,
        ?int $networkId,
        array $row,
        ?int $defaultVendorId
    ): array {
        $planId = $this->toPositiveInt($row['plan_id'] ?? null);
        if (! $planId) {
            return ['status' => 'error', 'message' => 'plan_id is required and must be a positive integer.'];
        }

        $vendorId = $this->toPositiveInt($row['vendor_id'] ?? null) ?: $defaultVendorId;
        if (! $vendorId) {
            return ['status' => 'error', 'message' => 'vendor_id is required (or set a default vendor).'];
        }

        $vendorPlanCode = trim((string) ($row['vendor_plan_code'] ?? ''));
        if ($vendorPlanCode === '') {
            return ['status' => 'error', 'message' => 'vendor_plan_code is required.'];
        }

        $plan = Plan::query()
            ->where('id', $planId)
            ->where('category_id', $category->id)
            ->when($networkId !== null, fn ($query) => $query->where('network_id', $networkId))
            ->first();
        if (! $plan) {
            return ['status' => 'error', 'message' => 'Plan not found in selected scope.'];
        }

        $vendor = Vendor::query()->find($vendorId);
        if (! $vendor) {
            return ['status' => 'error', 'message' => 'Vendor not found.'];
        }

        if (! $this->vendorSupportsCategory($vendor, $category->slug)) {
            return ['status' => 'error', 'message' => "Vendor type [{$vendor->type}] is not supported for this category."];
        }

        $status = in_array(($row['status'] ?? 'active'), ['active', 'inactive'], true)
            ? (string) ($row['status'] ?? 'active')
            : 'active';
        $forceVendor = $this->toBool($row['force_vendor'] ?? false);
        $requestedPriority = $this->toPositiveInt($row['priority'] ?? null);

        $outcome = DB::transaction(function () use (
            $plan,
            $vendorId,
            $vendorPlanCode,
            $status,
            $forceVendor,
            $requestedPriority
        ): string {
            $mapping = PlanVendorMapping::query()
                ->where('plan_id', $plan->id)
                ->where('vendor_id', $vendorId)
                ->first();

            if ($mapping) {
                $dirty = false;

                if ($mapping->vendor_plan_code !== $vendorPlanCode) {
                    $mapping->vendor_plan_code = $vendorPlanCode;
                    $dirty = true;
                }

                if ($mapping->status !== $status) {
                    $mapping->status = $status;
                    $dirty = true;
                }

                if ((bool) $mapping->force_vendor !== $forceVendor) {
                    $mapping->force_vendor = $forceVendor;
                    $dirty = true;
                }

                if ($requestedPriority !== null) {
                    $resolvedPriority = $this->resolvePriority($plan->id, $requestedPriority, $mapping->id);
                    if ((int) $mapping->priority !== $resolvedPriority) {
                        $mapping->priority = $resolvedPriority;
                        $dirty = true;
                    }
                }

                if ($dirty) {
                    $mapping->save();
                    if ($forceVendor) {
                        $plan->force_vendor_id = $vendorId;
                        $plan->save();
                    }

                    return 'updated';
                }

                return 'skipped';
            }

            $priority = $requestedPriority !== null
                ? $this->resolvePriority($plan->id, $requestedPriority, null)
                : $this->nextAvailablePriority($plan->id);

            PlanVendorMapping::query()->create([
                'plan_id' => $plan->id,
                'vendor_id' => $vendorId,
                'vendor_plan_code' => $vendorPlanCode,
                'priority' => $priority,
                'status' => $status,
                'force_vendor' => $forceVendor,
                'meta_json' => null,
            ]);

            if ($forceVendor) {
                $plan->force_vendor_id = $vendorId;
                $plan->save();
            }

            return 'created';
        });

        return [
            'status' => $outcome,
            'message' => 'ok',
        ];
    }

    /**
     * @param array<int,string> $header
     * @return array<string,int>
     */
    protected function buildColumnMap(array $header): array
    {
        $map = [];

        foreach ($header as $index => $name) {
            $normalized = $this->normalizeHeader((string) $name);
            $canonical = match ($normalized) {
                'planid', 'plan' => 'plan_id',
                'vendorid', 'vendor' => 'vendor_id',
                'vendorplancode', 'vendorplanid', 'plancode', 'code', 'variationcode' => 'vendor_plan_code',
                'priority' => 'priority',
                'status' => 'status',
                'forcevendor', 'force' => 'force_vendor',
                default => null,
            };

            if ($canonical !== null && ! array_key_exists($canonical, $map)) {
                $map[$canonical] = $index;
            }
        }

        return $map;
    }

    /**
     * @param array<string,int> $columnMap
     * @return array<int,string>
     */
    protected function missingRequiredColumns(array $columnMap, ?int $defaultVendorId): array
    {
        $required = ['plan_id', 'vendor_plan_code'];
        if (! $defaultVendorId) {
            $required[] = 'vendor_id';
        }

        return collect($required)
            ->filter(fn ($column) => ! array_key_exists($column, $columnMap))
            ->values()
            ->all();
    }

    /**
     * @param array<int,mixed> $row
     * @param array<string,int> $columnMap
     * @return array<string,mixed>
     */
    protected function extractRowPayload(array $row, array $columnMap): array
    {
        $payload = [];
        foreach ($columnMap as $key => $index) {
            $payload[$key] = trim((string) ($row[$index] ?? ''));
        }

        return $payload;
    }

    protected function isBlankRow(array $row): bool
    {
        foreach ($row as $cell) {
            if (trim((string) $cell) !== '') {
                return false;
            }
        }

        return true;
    }

    protected function normalizeHeader(string $header): string
    {
        return strtolower(preg_replace('/[^a-z0-9]+/', '', trim($header)));
    }

    protected function toPositiveInt(mixed $value): ?int
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (! is_numeric($value)) {
            return null;
        }

        $int = (int) $value;

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

    protected function toBool(mixed $value): bool
    {
        $normalized = strtolower(trim((string) $value));

        return in_array($normalized, ['1', 'true', 'yes', 'y', 'on'], true);
    }

    protected function nextAvailablePriority(int $planId): int
    {
        return ((int) (PlanVendorMapping::query()
            ->where('plan_id', $planId)
            ->max('priority') ?? 0)) + 1;
    }

    protected function resolvePriority(int $planId, int $desiredPriority, ?int $ignoreMappingId = null): int
    {
        $priority = max(1, $desiredPriority);

        while (
            PlanVendorMapping::query()
                ->where('plan_id', $planId)
                ->where('priority', $priority)
                ->when($ignoreMappingId !== null, fn ($query) => $query->where('id', '!=', $ignoreMappingId))
                ->exists()
        ) {
            $priority++;
        }

        return $priority;
    }

    protected function supportsPlanCodeImport(string $categorySlug): bool
    {
        return in_array($categorySlug, ['data_sme', 'data_gifting', 'data_cg', 'data_direct', 'cable'], true);
    }

    protected function vendorSupportsCategory(Vendor $vendor, string $categorySlug): bool
    {
        if ($vendor->type !== 'smeplug') {
            return true;
        }

        return in_array($categorySlug, ['data_sme', 'data_gifting', 'data_cg', 'data_direct'], true);
    }
}
