<?php

declare(strict_types=1);

namespace App\Business\MasterData;

use App\Business\MasterData\Enums\RegionLevel;
use App\Models\DberpPurchaseOrder;
use App\Models\DberpPurchaseReceiptOrder;
use App\Models\DberpRegion;
use App\Models\DberpSalesOrder;
use App\Models\DberpSalesOutboundOrder;
use App\Models\DberpTransferOrder;
use App\Services\DberpAuditService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class DberpRegionGovernanceService
{
    /**
     * 后台表单保存时禁止用户直接改写的字段（不含 parent_id，见 {@see self::ensureCanChangeParent()}）。
     *
     * @var list<string>
     */
    private const ADMIN_IMMUTABLE_ATTRIBUTE_KEYS = [
        'level',
        'depth',
        'path',
        'merger_name',
        'country_code',
        'region_code',
        'external_code',
        'source',
        'is_leaf',
    ];

    public function __construct(
        private readonly DberpAuditService $auditService,
    ) {}

    /**
     * @return array<int, string>
     */
    public function provinceOptions(?string $countryCode, ?int $currentRegionId = null): array
    {
        if (blank($countryCode)) {
            return [];
        }

        return $this->selectableRegionQuery($currentRegionId)
            ->forCountry((string) $countryCode)
            ->where('level', 'province')
            ->orderBy('sort_order')
            ->orderBy('id')
            ->pluck('name', 'id')
            ->mapWithKeys(fn (string $name, int|string $id): array => [(int) $id => $name])
            ->all();
    }

    /**
     * @return array<int, string>
     */
    public function cityOptions(int|string|null $provinceRegionId, ?int $currentRegionId = null): array
    {
        if (blank($provinceRegionId)) {
            return [];
        }

        return $this->selectableRegionQuery($currentRegionId)
            ->where('parent_id', (int) $provinceRegionId)
            ->where('level', 'city')
            ->orderBy('sort_order')
            ->orderBy('id')
            ->pluck('name', 'id')
            ->mapWithKeys(fn (string $name, int|string $id): array => [(int) $id => $name])
            ->all();
    }

    /**
     * @return array<int, string>
     */
    public function districtOptions(int|string|null $cityRegionId, ?int $currentRegionId = null): array
    {
        if (blank($cityRegionId)) {
            return [];
        }

        return $this->selectableRegionQuery($currentRegionId)
            ->where('parent_id', (int) $cityRegionId)
            ->where('level', 'district')
            ->orderBy('sort_order')
            ->orderBy('id')
            ->pluck('name', 'id')
            ->mapWithKeys(fn (string $name, int|string $id): array => [(int) $id => $name])
            ->all();
    }

    /**
     * 主资料（客户/供应商/仓库）地区：仅绑定到「区县」节点；不要求末级叶子，以便存在下级手工节点时仍可选区县。
     */
    public function resolveMasterDataDistrictRegion(?int $regionId, ?DberpRegion $currentRegion = null): ?DberpRegion
    {
        if ($regionId === null) {
            return null;
        }

        $region = DberpRegion::query()->find($regionId);

        if (! $region instanceof DberpRegion) {
            throw ValidationException::withMessages([
                'district_region_id' => __('dberp.errors.region_not_found'),
            ]);
        }

        $district = $region;

        if ((string) $district->level === RegionLevel::Street->value) {
            $district = $this->findAncestorDistrict($region) ?? $district;
        }

        if (! $district instanceof DberpRegion || (string) $district->level !== RegionLevel::District->value) {
            throw ValidationException::withMessages([
                'district_region_id' => __('dberp.errors.region_master_data_must_be_district'),
            ]);
        }

        if ($currentRegion instanceof DberpRegion && $currentRegion->getKey() === $district->getKey()) {
            return $district;
        }

        if (! $district->is_enabled) {
            $this->logBlocked('dberp.region.selection_blocked', $district, [
                'reason' => 'disabled',
            ]);

            throw ValidationException::withMessages([
                'district_region_id' => __('dberp.errors.region_disabled_not_selectable'),
            ]);
        }

        return $district;
    }

    private function findAncestorDistrict(DberpRegion $region): ?DberpRegion
    {
        $cursor = $region->parent_id === null
            ? null
            : DberpRegion::query()->find($region->parent_id);

        while ($cursor instanceof DberpRegion) {
            if ((string) $cursor->level === RegionLevel::District->value) {
                return $cursor;
            }

            $cursor = $cursor->parent_id === null
                ? null
                : DberpRegion::query()->find($cursor->parent_id);
        }

        return null;
    }

    public function ensureCanDelete(DberpRegion $region): void
    {
        $reasons = $this->deleteBlockReasons($region);

        if ($reasons === []) {
            return;
        }

        $this->logBlocked('dberp.region.delete_blocked', $region, ['reasons' => $reasons]);

        throw ValidationException::withMessages([
            'record' => implode('；', $reasons),
        ]);
    }

    public function ensureCanDisable(DberpRegion $region): void
    {
        $reasons = $this->disableBlockReasons($region);

        if ($reasons === []) {
            return;
        }

        $this->logBlocked('dberp.region.disable_blocked', $region, ['reasons' => $reasons]);

        throw ValidationException::withMessages([
            'is_enabled' => implode('；', $reasons),
        ]);
    }

    public function ensureCanChangeParent(DberpRegion $region, ?int $newParentId): void
    {
        $originalParentId = $region->getOriginal('parent_id');
        $originalParentId = $originalParentId === null ? null : (int) $originalParentId;

        if ($originalParentId === $newParentId) {
            return;
        }

        $originalSource = (string) ($region->getOriginal('source') ?? '');

        if (DberpRegionSource::isOfficialImported($originalSource)) {
            $this->logBlocked('dberp.region.parent_change_blocked', $region, [
                'original_parent_id' => $originalParentId,
                'new_parent_id' => $newParentId,
                'reason' => 'official_imported',
            ]);

            throw ValidationException::withMessages([
                'parent_id' => __('dberp.errors.region_parent_change_blocked_official'),
            ]);
        }

        if (DberpRegionSource::isSystem($originalSource)) {
            $this->logBlocked('dberp.region.parent_change_blocked', $region, [
                'original_parent_id' => $originalParentId,
                'new_parent_id' => $newParentId,
                'reason' => 'system',
            ]);

            throw ValidationException::withMessages([
                'parent_id' => __('dberp.errors.region_parent_change_blocked_system'),
            ]);
        }

        if (DberpRegionSource::isManual($originalSource) && $this->isRegionBlockingParentChangeByReferences($region)) {
            $this->logBlocked('dberp.region.parent_change_blocked', $region, [
                'original_parent_id' => $originalParentId,
                'new_parent_id' => $newParentId,
                'reason' => 'referenced',
            ]);

            throw ValidationException::withMessages([
                'parent_id' => __('dberp.errors.region_parent_change_blocked_in_use'),
            ]);
        }

        $this->logBlocked('dberp.region.parent_change_blocked', $region, [
            'original_parent_id' => $originalParentId,
            'new_parent_id' => $newParentId,
            'reason' => 'policy',
        ]);

        throw ValidationException::withMessages([
            'parent_id' => __('dberp.errors.region_parent_change_restricted'),
        ]);
    }

    public function ensureAdminUpdateAllowsDirtyAttributes(DberpRegion $region): void
    {
        $originalSource = (string) ($region->getOriginal('source') ?? '');

        foreach (self::ADMIN_IMMUTABLE_ATTRIBUTE_KEYS as $key) {
            if (! $region->isDirty($key)) {
                continue;
            }

            if (DberpRegionSource::isManual($originalSource)) {
                throw ValidationException::withMessages([
                    $key => __('dberp.errors.region_manual_structural_locked'),
                ]);
            }

            throw ValidationException::withMessages([
                $key => __('dberp.errors.region_official_imported_field_locked'),
            ]);
        }
    }

    public function disableRecords(Collection $records): void
    {
        DB::transaction(function () use ($records): void {
            $records->each(function (DberpRegion $region): void {
                if (! $region->is_enabled) {
                    return;
                }

                $region->update(['is_enabled' => false]);
            });
        });
    }

    public function enableRecords(Collection $records): void
    {
        DB::transaction(function () use ($records): void {
            $records->each(function (DberpRegion $region): void {
                if ($region->is_enabled) {
                    return;
                }

                $region->update(['is_enabled' => true]);
            });
        });
    }

    public function usageSummary(DberpRegion $region): string
    {
        $parts = [];

        $customerCount = (int) ($region->customers_count ?? $region->customers()->count());
        $supplierCount = (int) ($region->suppliers_count ?? $region->suppliers()->count());
        $warehouseCount = (int) ($region->warehouses_count ?? $region->warehouses()->count());

        if ($customerCount > 0) {
            $parts[] = __('dberp.messages.region_usage_customer', ['count' => $customerCount]);
        }

        if ($supplierCount > 0) {
            $parts[] = __('dberp.messages.region_usage_supplier', ['count' => $supplierCount]);
        }

        if ($warehouseCount > 0) {
            $parts[] = __('dberp.messages.region_usage_warehouse', ['count' => $warehouseCount]);
        }

        return $parts === []
            ? __('dberp.messages.region_usage_none')
            : implode('；', $parts);
    }

    private function selectableRegionQuery(?int $currentRegionId = null): Builder
    {
        return DberpRegion::query()
            ->where(function (Builder $query) use ($currentRegionId): void {
                $query->where('is_enabled', true);

                if ($currentRegionId !== null) {
                    $query->orWhere((new DberpRegion)->getQualifiedKeyName(), $currentRegionId);
                }
            });
    }

    /**
     * @return list<string>
     */
    private function deleteBlockReasons(DberpRegion $region): array
    {
        $reasons = [];

        if (! DberpRegionSource::isManual((string) $region->source)) {
            $reasons[] = __('dberp.errors.region_delete_only_manual');
        }

        if ($region->children()->exists()) {
            $reasons[] = __('dberp.errors.region_has_children');
        }

        if ($region->customers()->exists()) {
            $reasons[] = __('dberp.errors.region_in_use_by_customer');
        }

        if ($region->suppliers()->exists()) {
            $reasons[] = __('dberp.errors.region_in_use_by_supplier');
        }

        if ($region->warehouses()->exists()) {
            $reasons[] = __('dberp.errors.region_in_use_by_warehouse');
        }

        if ($this->hasHistoricalSnapshotUsage($region)) {
            $reasons[] = __('dberp.errors.region_used_in_history_documents');
        }

        return $reasons;
    }

    private function isRegionBlockingParentChangeByReferences(DberpRegion $region): bool
    {
        if ($region->customers()->exists()) {
            return true;
        }

        if ($region->suppliers()->exists()) {
            return true;
        }

        if ($region->warehouses()->exists()) {
            return true;
        }

        return $this->hasHistoricalSnapshotUsage($region);
    }

    /**
     * @return list<string>
     */
    private function disableBlockReasons(DberpRegion $region): array
    {
        $reasons = [];

        if ($region->children()->where('is_enabled', true)->exists()) {
            $reasons[] = __('dberp.errors.region_has_enabled_children');
        }

        return $reasons;
    }

    private function hasHistoricalSnapshotUsage(DberpRegion $region): bool
    {
        if (blank($region->merger_name)) {
            return false;
        }

        $snapshot = (string) $region->merger_name;

        return DberpPurchaseOrder::query()->where('supplier_region_name_snapshot', $snapshot)->exists()
            || DberpPurchaseReceiptOrder::query()->where('supplier_region_name_snapshot', $snapshot)->exists()
            || DberpPurchaseReceiptOrder::query()->where('warehouse_region_name_snapshot', $snapshot)->exists()
            || DberpSalesOrder::query()->where('customer_region_name_snapshot', $snapshot)->exists()
            || DberpSalesOutboundOrder::query()->where('customer_region_name_snapshot', $snapshot)->exists()
            || DberpSalesOutboundOrder::query()->where('warehouse_region_name_snapshot', $snapshot)->exists()
            || DberpTransferOrder::query()->where('from_warehouse_region_name_snapshot', $snapshot)->exists()
            || DberpTransferOrder::query()->where('to_warehouse_region_name_snapshot', $snapshot)->exists();
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function logBlocked(string $action, DberpRegion $region, array $payload): void
    {
        if (! Auth::check()) {
            return;
        }

        $this->auditService->log($action, $region, null, $payload);
    }
}
