<?php

namespace App\Services;

use App\Models\Category;
use App\Models\Network;
use App\Models\Plan;
use App\Models\PlanVendorMapping;
use App\Models\ServiceVendorMapping;
use App\Models\Vendor;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class MappingAutoFixService
{
    /**
     * Auto-fix routing for one scope (category + optional network).
     *
     * @return array{
     *   scope: array{category_id:int,category_slug:string,network_id:int|null,network_slug:string|null},
     *   created_service_mappings:int,
     *   updated_service_mappings:int,
     *   created_plan_mappings:int,
     *   updated_plan_mappings:int,
     *   unresolved_plans_count:int,
     *   unresolved_plans: array<int, array{plan_id:int,plan_name:string,reason:string}>,
     *   applied_vendor_ids: int[]
     * }
     */
    public function autoFix(int $categoryId, ?int $networkId = null): array
    {
        $category = Category::query()->findOrFail($categoryId);
        $network = $networkId ? Network::query()->findOrFail($networkId) : null;

        $scopeMappings = $this->scopeMappings($categoryId, $networkId)
            ->with('vendor:id,name,type,status')
            ->orderBy('priority')
            ->get();

        $candidateVendors = $this->resolveCandidateVendors($category, $network, $scopeMappings);

        $createdServiceMappings = 0;
        $updatedServiceMappings = 0;

        DB::transaction(function () use (
            $categoryId,
            $networkId,
            $scopeMappings,
            $candidateVendors,
            &$createdServiceMappings,
            &$updatedServiceMappings
        ) {
            $hasActiveScopeRoute = $scopeMappings->contains(
                fn (ServiceVendorMapping $mapping) => $mapping->status === 'active'
                    && $mapping->vendor
                    && $mapping->vendor->status === 'active'
            );

            if ($hasActiveScopeRoute) {
                return;
            }

            foreach ($candidateVendors->values() as $index => $vendor) {
                $priority = $index + 1;
                $existing = $this->scopeMappings($categoryId, $networkId)
                    ->where('vendor_id', $vendor->id)
                    ->first();

                if (! $existing) {
                    ServiceVendorMapping::query()->create([
                        'category_id' => $categoryId,
                        'network_id' => $networkId,
                        'vendor_id' => $vendor->id,
                        'priority' => $priority,
                        'status' => 'active',
                        'force_vendor' => false,
                        'meta_json' => null,
                    ]);
                    $createdServiceMappings++;
                    continue;
                }

                $dirty = false;
                if ($existing->status !== 'active') {
                    $existing->status = 'active';
                    $dirty = true;
                }
                if ((int) $existing->priority !== $priority) {
                    $existing->priority = $priority;
                    $dirty = true;
                }

                if ($dirty) {
                    $existing->save();
                    $updatedServiceMappings++;
                }
            }
        });

        $activeScopeMappings = $this->scopeMappings($categoryId, $networkId)
            ->where('status', 'active')
            ->with('vendor:id,name,type,status')
            ->orderBy('priority')
            ->get();

        $appliedVendors = $activeScopeMappings
            ->map(fn (ServiceVendorMapping $mapping) => $mapping->vendor)
            ->filter(fn (?Vendor $vendor) => $vendor && $vendor->status === 'active')
            ->values();

        $plans = Plan::query()
            ->with('vendorMappings')
            ->where('category_id', $categoryId)
            ->when($networkId !== null, fn ($query) => $query->where('network_id', $networkId))
            ->whereIn('status', ['available', 'maintenance'])
            ->orderBy('sort_order')
            ->orderBy('id')
            ->get();

        $createdPlanMappings = 0;
        $updatedPlanMappings = 0;
        $unresolvedPlans = [];

        if ($this->categoryRequiresPlanCode($category->slug) && $appliedVendors->isNotEmpty()) {
            foreach ($plans as $plan) {
                $hasActivePlanRoute = $plan->vendorMappings
                    ->contains(fn (PlanVendorMapping $mapping) => $mapping->status === 'active');

                if ($hasActivePlanRoute) {
                    continue;
                }

                $byVendor = $plan->vendorMappings->keyBy('vendor_id');
                $nextPriority = ((int) ($plan->vendorMappings->max('priority') ?? 0)) + 1;
                $resolved = false;

                foreach ($appliedVendors as $vendor) {
                    if (! $this->vendorSupportsService($vendor, $this->serviceKeyFromCategorySlug($category->slug))) {
                        continue;
                    }

                    $existingForVendor = $byVendor->get($vendor->id);
                    $existingCode = trim((string) ($existingForVendor?->vendor_plan_code ?? ''));
                    $vendorPlanCode = $existingCode !== '' ? $existingCode : $this->inferVendorPlanCode($plan, $vendor);

                    if ($vendorPlanCode === null || $vendorPlanCode === '') {
                        continue;
                    }

                    if ($existingForVendor) {
                        $dirty = false;

                        if ($existingForVendor->vendor_plan_code !== $vendorPlanCode) {
                            $existingForVendor->vendor_plan_code = $vendorPlanCode;
                            $dirty = true;
                        }
                        if ($existingForVendor->status !== 'active') {
                            $existingForVendor->status = 'active';
                            $dirty = true;
                        }

                        if ($dirty) {
                            $existingForVendor->save();
                            $updatedPlanMappings++;
                        }
                    } else {
                        PlanVendorMapping::query()->create([
                            'plan_id' => $plan->id,
                            'vendor_id' => $vendor->id,
                            'vendor_plan_code' => $vendorPlanCode,
                            'priority' => $nextPriority,
                            'status' => 'active',
                            'force_vendor' => false,
                            'meta_json' => null,
                        ]);
                        $createdPlanMappings++;
                    }

                    $resolved = true;
                    break;
                }

                if (! $resolved) {
                    $unresolvedPlans[] = [
                        'plan_id' => (int) $plan->id,
                        'plan_name' => (string) $plan->name,
                        'reason' => 'Vendor plan code is missing in plan metadata.',
                    ];
                }
            }
        }

        return [
            'scope' => [
                'category_id' => (int) $category->id,
                'category_slug' => (string) $category->slug,
                'network_id' => $network?->id ? (int) $network->id : null,
                'network_slug' => $network?->slug ? (string) $network->slug : null,
            ],
            'created_service_mappings' => $createdServiceMappings,
            'updated_service_mappings' => $updatedServiceMappings,
            'created_plan_mappings' => $createdPlanMappings,
            'updated_plan_mappings' => $updatedPlanMappings,
            'unresolved_plans_count' => count($unresolvedPlans),
            'unresolved_plans' => $unresolvedPlans,
            'applied_vendor_ids' => $appliedVendors->pluck('id')->map(fn ($id) => (int) $id)->values()->all(),
        ];
    }

    protected function resolveCandidateVendors(
        Category $category,
        ?Network $network,
        Collection $scopeMappings
    ): Collection {
        $serviceKey = $this->serviceKeyFromCategorySlug($category->slug);
        $networkId = $network?->id;

        $ids = collect();

        $ids = $ids->merge(
            $scopeMappings
                ->sortBy('priority')
                ->pluck('vendor_id')
        );

        $ids = $ids->merge(
            ServiceVendorMapping::query()
                ->where('category_id', $category->id)
                ->where('status', 'active')
                ->when(
                    $networkId !== null,
                    fn ($query) => $query->where(function ($sub) use ($networkId) {
                        $sub->where('network_id', $networkId)->orWhereNull('network_id');
                    }),
                    fn ($query) => $query->whereNull('network_id')
                )
                ->orderBy('priority')
                ->pluck('vendor_id')
        );

        $ids = $ids->merge(
            PlanVendorMapping::query()
                ->join('plans', 'plans.id', '=', 'plan_vendor_mappings.plan_id')
                ->where('plans.category_id', $category->id)
                ->when($networkId !== null, fn ($query) => $query->where('plans.network_id', $networkId))
                ->where('plan_vendor_mappings.status', 'active')
                ->groupBy('plan_vendor_mappings.vendor_id')
                ->orderByRaw('COUNT(*) DESC')
                ->pluck('plan_vendor_mappings.vendor_id')
        );

        $ids = $ids->merge(
            Vendor::query()
                ->where('status', 'active')
                ->orderBy('type')
                ->orderBy('name')
                ->pluck('id')
        );

        $vendors = Vendor::query()
            ->whereIn('id', $ids->filter()->map(fn ($id) => (int) $id)->unique()->values())
            ->where('status', 'active')
            ->get()
            ->keyBy('id');

        return $ids
            ->filter()
            ->map(fn ($id) => $vendors->get((int) $id))
            ->filter(fn (?Vendor $vendor) => $vendor !== null)
            ->filter(fn (Vendor $vendor) => $this->vendorSupportsService($vendor, $serviceKey))
            ->filter(fn (Vendor $vendor) => $this->vendorHasRequiredNetworkCode($vendor, $network, $serviceKey))
            ->unique(fn (Vendor $vendor) => $vendor->id)
            ->values();
    }

    protected function vendorSupportsService(Vendor $vendor, string $serviceKey): bool
    {
        if ($vendor->type === 'smeplug') {
            return in_array($serviceKey, ['data', 'airtime'], true);
        }

        return in_array($serviceKey, ['data', 'airtime', 'cable', 'electricity', 'epin'], true);
    }

    protected function vendorHasRequiredNetworkCode(Vendor $vendor, ?Network $network, string $serviceKey): bool
    {
        if (! $network) {
            return true;
        }

        if (! in_array($serviceKey, ['data', 'airtime'], true)) {
            return true;
        }

        $codes = (array) data_get($network->vendor_network_code_json, $vendor->type, []);

        if ($vendor->type === 'smeplug') {
            return filled($codes['network_id'] ?? null);
        }

        if (in_array($vendor->type, ['msorg', 'vtbox'], true)) {
            return filled($codes[$serviceKey] ?? null) || filled($codes['network_id'] ?? null);
        }

        return true;
    }

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

    protected function inferVendorPlanCode(Plan $plan, Vendor $vendor): ?string
    {
        $meta = (array) ($plan->meta_json ?? []);

        $candidates = [];

        if ($vendor->type === 'vtpass') {
            $candidates = [
                $meta['vtpass_variation_code'] ?? null,
                $meta['variation_code'] ?? null,
                $meta['vendor_plan_code'] ?? null,
                $meta['plan_code'] ?? null,
                $meta['code'] ?? null,
            ];
        } else {
            $candidates = [
                $meta[$vendor->type.'_plan_code'] ?? null,
                $meta['vendor_plan_code'] ?? null,
                $meta['plan_code'] ?? null,
                $meta['code'] ?? null,
            ];
        }

        foreach ($candidates as $candidate) {
            $value = trim((string) ($candidate ?? ''));
            if ($value !== '') {
                return $value;
            }
        }

        return null;
    }

    protected function scopeMappings(int $categoryId, ?int $networkId)
    {
        return ServiceVendorMapping::query()
            ->where('category_id', $categoryId)
            ->when(
                $networkId !== null,
                fn ($query) => $query->where('network_id', $networkId),
                fn ($query) => $query->whereNull('network_id')
            );
    }

    protected function serviceKeyFromCategorySlug(string $slug): string
    {
        return match ($slug) {
            'data_sme', 'data_gifting', 'data_cg', 'data_direct' => 'data',
            'airtime_vtu' => 'airtime',
            'cable' => 'cable',
            'electricity' => 'electricity',
            'epin' => 'epin',
            default => $slug,
        };
    }
}
