<?php

namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use App\Models\Group;
use App\Models\Number;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class ContactSyncController extends Controller
{
    /**
     * Bidirectionally synchronize contacts & groups between mobile app and backend
     * with chunked processing support for large datasets.
     */
    public function sync(Request $request): JsonResponse
    {
        $user = $request->user();
        $userId = (int) $user->id;

        $rawGroups = $request->input('groups', $request->input('categories', []));
        $rawContacts = $request->input('contacts', $request->input('numbers', []));
        $chunkIndex = $request->has('chunk_index') ? (int) $request->input('chunk_index') : null;
        $totalChunks = $request->has('total_chunks') ? (int) $request->input('total_chunks') : null;
        $isLastChunk = $request->has('is_last_chunk') ? $request->boolean('is_last_chunk') : null;
        $fetchOnly = $request->boolean('fetch_only', false);
        $page = max(1, (int) $request->input('page', 1));
        $perPage = (int) $request->input('per_page', 0);

        if (!is_array($rawGroups)) {
            $rawGroups = [];
        }
        if (!is_array($rawContacts)) {
            $rawContacts = [];
        }

        // 1. Process upload data unless it's a fetch_only request
        if (!$fetchOnly) {
            // Synchronize groups if provided
            if (!empty($rawGroups)) {
                DB::transaction(function () use ($userId, $rawGroups): void {
                    $existingGroups = Group::where('user_id', $userId)->get();
                    $groupByName = [];
                    foreach ($existingGroups as $grp) {
                        $groupByName[mb_strtolower(trim($grp->name))] = $grp;
                    }

                    foreach ($rawGroups as $g) {
                        if (!is_array($g)) {
                            continue;
                        }
                        $name = trim((string) ($g['name'] ?? $g['group_name'] ?? ''));
                        if ($name === '') {
                            continue;
                        }

                        $desc = isset($g['description']) ? trim((string) $g['description']) : null;
                        $color = isset($g['color']) ? trim((string) $g['color']) : '#246bfd';
                        $nameKey = mb_strtolower($name);

                        if (isset($groupByName[$nameKey])) {
                            $existing = $groupByName[$nameKey];
                            $existing->description = $desc;
                            $existing->color = $color ?: '#246bfd';
                            $existing->save();
                        } else {
                            $newGroup = Group::create([
                                'user_id' => $userId,
                                'name' => $name,
                                'description' => $desc,
                                'color' => $color ?: '#246bfd',
                            ]);
                            $groupByName[$nameKey] = $newGroup;
                        }
                    }
                });
            }

            // Synchronize incoming contacts in efficient database batches/chunks
            if (!empty($rawContacts)) {
                // Pre-load all user groups for quick in-memory group resolution
                $allGroups = Group::where('user_id', $userId)->get();
                $groupLookup = [];
                foreach ($allGroups as $grp) {
                    $groupLookup[mb_strtolower(trim($grp->name))] = $grp;
                    $groupLookup['id_' . $grp->id] = $grp;
                }

                // Chunk incoming contacts to prevent query/memory timeouts with large arrays
                $contactBatches = array_chunk($rawContacts, 250);

                foreach ($contactBatches as $batch) {
                    DB::transaction(function () use ($userId, $batch, &$groupLookup): void {
                        // Extract and clean all phones in this batch
                        $cleanedBatch = [];
                        $cleanPhones = [];

                        foreach ($batch as $c) {
                            if (!is_array($c)) {
                                continue;
                            }
                            $phone = trim((string) ($c['number'] ?? $c['phone'] ?? ''));
                            $cleanPhone = preg_replace('/[^\d+]/', '', $phone);
                            if (empty($cleanPhone)) {
                                continue;
                            }
                            $cleanedBatch[] = [
                                'raw' => $c,
                                'cleanPhone' => $cleanPhone,
                            ];
                            $cleanPhones[] = $cleanPhone;
                        }

                        if (empty($cleanPhones)) {
                            return;
                        }

                        // Bulk query existing numbers in this batch (1 query instead of 250 individual queries)
                        $existingContacts = Number::where('user_id', $userId)
                            ->whereIn('number', array_unique($cleanPhones))
                            ->get()
                            ->keyBy('number');

                        foreach ($cleanedBatch as $item) {
                            $c = $item['raw'];
                            $cleanPhone = $item['cleanPhone'];

                            $contactName = isset($c['name']) ? trim((string) $c['name']) : null;
                            $notes = isset($c['notes']) ? trim((string) $c['notes']) : null;

                            // Resolve group association
                            $matchedGroupId = null;
                            $grpName = isset($c['group_name'])
                                ? mb_strtolower(trim((string) $c['group_name']))
                                : (isset($c['category_name']) ? mb_strtolower(trim((string) $c['category_name'])) : '');
                            $grpId = isset($c['group_id'])
                                ? (int) $c['group_id']
                                : (isset($c['category_id']) ? (int) $c['category_id'] : 0);

                            if ($grpName !== '' && isset($groupLookup[$grpName])) {
                                $matchedGroupId = $groupLookup[$grpName]->id;
                            } elseif ($grpId > 0 && isset($groupLookup['id_' . $grpId])) {
                                $matchedGroupId = $groupLookup['id_' . $grpId]->id;
                            } elseif ($grpName !== '') {
                                $rawOriginalName = (string) ($c['group_name'] ?? $c['category_name'] ?? '');
                                $createdGroup = Group::create([
                                    'user_id' => $userId,
                                    'name' => trim($rawOriginalName),
                                    'description' => null,
                                    'color' => '#246bfd',
                                ]);
                                $groupLookup[$grpName] = $createdGroup;
                                $groupLookup['id_' . $createdGroup->id] = $createdGroup;
                                $matchedGroupId = $createdGroup->id;
                            }

                            if ($existingContacts->has($cleanPhone)) {
                                $existingContact = $existingContacts->get($cleanPhone);
                                $existingContact->name = !empty($contactName) ? $contactName : $existingContact->name;
                                $existingContact->notes = $notes !== null ? $notes : $existingContact->notes;
                                $existingContact->group_id = $matchedGroupId ?: 0;
                                $existingContact->number = $cleanPhone;
                                $existingContact->save();
                            } else {
                                $newContact = Number::create([
                                    'user_id' => $userId,
                                    'group_id' => $matchedGroupId ?: 0,
                                    'name' => $contactName,
                                    'number' => $cleanPhone,
                                    'notes' => $notes,
                                ]);
                                $existingContacts->put($cleanPhone, $newContact);
                            }
                        }
                    });
                }
            }
        }

        // If this is an intermediate upload chunk, return quick chunk acknowledgment
        if ($isLastChunk === false || ($chunkIndex !== null && $totalChunks !== null && $chunkIndex < $totalChunks - 1)) {
            return response()->json([
                'status' => 2000,
                'success' => true,
                'message' => 'Contacts chunk synchronized successfully.',
                'synced_at' => now()->toIso8601String(),
                'chunk_index' => $chunkIndex,
                'total_chunks' => $totalChunks,
                'is_last_chunk' => false,
                'processed_contacts' => count($rawContacts),
            ]);
        }

        // 2. Fetch consolidated Groups from groups table
        $groups = Group::where('user_id', $userId)->orderBy('name', 'asc')->get();

        // Optimized group counts via single aggregation query
        $countsPerGroup = Number::where('user_id', $userId)
            ->select('group_id', DB::raw('COUNT(*) as total'))
            ->groupBy('group_id')
            ->pluck('total', 'group_id')
            ->all();

        $groupsResult = $groups->map(function (Group $g) use ($countsPerGroup): array {
            return [
                'id' => (int) $g->id,
                'name' => (string) $g->name,
                'group_name' => (string) $g->name,
                'description' => $g->description ? (string) $g->description : '',
                'color' => $g->color ?: '#246bfd',
                'numbers_count' => (int) ($countsPerGroup[$g->id] ?? 0),
                'created_at' => $g->created_at ? $g->created_at->toIso8601String() : null,
                'updated_at' => $g->updated_at ? $g->updated_at->toIso8601String() : null,
            ];
        })->values()->all();

        // 3. Fetch consolidated Numbers from numbers table (with optional pagination support)
        $groupMapById = $groups->keyBy('id');
        $totalNumbersCount = Number::where('user_id', $userId)->count();

        $numbersQuery = Number::where('user_id', $userId)->orderBy('id', 'desc');
        if ($perPage > 0) {
            $totalPages = (int) max(1, ceil($totalNumbersCount / $perPage));
            $numbersQuery = $numbersQuery->skip(($page - 1) * $perPage)->take($perPage);
            $hasMore = $page < $totalPages;
        } else {
            $totalPages = 1;
            $hasMore = false;
        }

        $numbersResult = $numbersQuery->get()
            ->map(function (Number $n) use ($groupMapById): array {
                $gid = $n->group_id ? (int) $n->group_id : null;
                $group = ($gid && $groupMapById->has($gid)) ? $groupMapById->get($gid) : null;
                $groupName = $group ? (string) $group->name : null;
                $phoneVal = $n->number;

                return [
                    'id' => (int) $n->id,
                    'group_id' => $group ? (int) $group->id : null,
                    'group_name' => $groupName,
                    'category_id' => $group ? (int) $group->id : null,
                    'category_name' => $groupName,
                    'name' => $n->name ? (string) $n->name : '',
                    'number' => (string) $phoneVal,
                    'phone' => (string) $phoneVal,
                    'notes' => $n->notes ? (string) $n->notes : '',
                    'created_at' => $n->created_at ? $n->created_at->toIso8601String() : null,
                    'updated_at' => $n->updated_at ? $n->updated_at->toIso8601String() : null,
                ];
            })
            ->values()
            ->all();

        $syncedAt = now()->toIso8601String();

        return response()->json([
            'status' => 2000,
            'success' => true,
            'message' => 'Contacts and groups synchronized successfully.',
            'synced_at' => $syncedAt,
            'groups' => $groupsResult,
            'categories' => $groupsResult,
            'contacts' => $numbersResult,
            'numbers' => $numbersResult,
            'is_last_chunk' => true,
            'pagination' => [
                'current_page' => $page,
                'per_page' => $perPage > 0 ? $perPage : $totalNumbersCount,
                'total_contacts' => $totalNumbersCount,
                'total_pages' => $totalPages,
                'has_more' => $hasMore,
            ],
            'stats' => [
                'groups_count' => count($groupsResult),
                'categories_count' => count($groupsResult),
                'contacts_count' => $totalNumbersCount,
            ],
        ]);
    }
}
