<?php

namespace Database\Seeders;

use App\Models\Central\Tenant;
use App\Models\Customers\Customer;
use App\Models\Customers\CustomerContact;
use App\Models\Customers\DeliveryAddress;
use App\Models\Geo\City;
use App\Models\User;
use App\Services\CurrentContext;
use Illuminate\Database\Seeder;

/**
 * Seeds the live customer book (exported from the production database) so a
 * fresh install already knows every active customer, their contacts, and
 * delivery addresses.
 *
 * Data lives in database/seeders/data/customers.json. Cities are resolved by
 * name against the geography seed; unmatched names fall back to `legacy_city`
 * so no address text is lost. Assigned reps are resolved by user email, so this
 * seeder must run AFTER {@see AccessControlSeeder}.
 */
class CustomerSeeder extends Seeder
{
    /** @var array<string, int|null> Lower-cased city name => city id (resolved lazily). */
    private array $cityCache = [];

    public function run(): void
    {
        $context = app(CurrentContext::class);

        if ($context->hasTenant()) {
            $this->seedForCurrentTenant();
        } else {
            Tenant::all()->each(function (Tenant $tenant) use ($context): void {
                $context->runForTenant($tenant, fn () => $this->seedForCurrentTenant());
            });
        }
    }

    private function seedForCurrentTenant(): void
    {
        $data = $this->readData();

        /** @var array<string, int> $userIds email => user id */
        $userIds = User::query()->pluck('id', 'email')->all();

        /** @var array<string, int> $customerIds natural key => customer id */
        $customerIds = [];

        foreach ($data['customers'] as $row) {
            [$cityId, $legacyCity] = $this->resolveCity($row['city'] ?? null, $row['legacy_city'] ?? null);

            $assignedEmail = $row['assigned_user_email'] ?? null;

            $customer = Customer::updateOrCreate(
                ['business_name' => $row['business_name'], 'phone' => $row['phone']],
                [
                    'nic' => $row['nic'] ?? null,
                    'email' => $row['email'] ?? null,
                    'street_line1' => $row['street_line1'] ?? null,
                    'street_line2' => $row['street_line2'] ?? null,
                    'city_id' => $cityId,
                    'legacy_city' => $legacyCity,
                    'assigned_user_id' => $assignedEmail ? ($userIds[$assignedEmail] ?? null) : null,
                    'is_active' => (bool) ($row['is_active'] ?? true),
                ],
            );

            $customerIds[$this->key($row['business_name'], $row['phone'])] = $customer->id;
        }

        foreach ($data['contacts'] as $row) {
            $customerId = $customerIds[$this->key($row['customer_business_name'], $row['customer_phone'])] ?? null;
            if ($customerId === null) {
                continue;
            }

            CustomerContact::firstOrCreate([
                'customer_id' => $customerId,
                'name' => $row['name'],
                'phone' => $row['phone'],
            ]);
        }

        foreach ($data['addresses'] as $row) {
            $customerId = $customerIds[$this->key($row['customer_business_name'], $row['customer_phone'])] ?? null;
            if ($customerId === null) {
                continue;
            }

            [$cityId, $legacyCity] = $this->resolveCity($row['city'] ?? null, $row['legacy_city'] ?? null);

            DeliveryAddress::firstOrCreate(
                [
                    'customer_id' => $customerId,
                    'street_line1' => $row['street_line1'] ?? null,
                    'city_id' => $cityId,
                ],
                [
                    'label' => $row['label'] ?? null,
                    'street_line2' => $row['street_line2'] ?? null,
                    'legacy_city' => $legacyCity,
                    'note' => $row['note'] ?? null,
                ],
            );
        }
    }

    /**
     * Resolve a free-text city name to a city id, keeping the original text in
     * `legacy_city` when it can't be matched (or when it was already legacy-only).
     *
     * @return array{0: int|null, 1: string|null} [city_id, legacy_city]
     */
    private function resolveCity(?string $cityName, ?string $legacyCity): array
    {
        $name = $cityName !== null ? trim($cityName) : '';
        if ($name === '') {
            return [null, $legacyCity];
        }

        $cacheKey = strtolower($name);
        if (! array_key_exists($cacheKey, $this->cityCache)) {
            $this->cityCache[$cacheKey] = City::query()->whereRaw('LOWER(name) = ?', [$cacheKey])->value('id');
        }

        $cityId = $this->cityCache[$cacheKey];

        return $cityId !== null ? [$cityId, $legacyCity] : [null, $name];
    }

    private function key(string $businessName, string $phone): string
    {
        return strtolower(trim($businessName)).'|'.trim($phone);
    }

    /**
     * @return array{customers: array<int, array<string, mixed>>, contacts: array<int, array<string, mixed>>, addresses: array<int, array<string, mixed>>}
     */
    private function readData(): array
    {
        return json_decode(file_get_contents(database_path('seeders/data/customers.json')), true);
    }
}
