<?php

namespace App\Services\Kyc;

use App\Models\UserDocument;
use App\Support\Audit;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Throwable;

/**
 * KYC document upload + verification — faithful Laravel port of kyc.php.
 * Files stored on the configured disk; doc_number PII-encrypted via the model.
 * Reviews audited into the shared audit_log.
 */
class KycService
{
    /**
     * KYC documents a user CATEGORY must / may provide (from the doc_types
     * master's multiselect `mandatory_for` / `optional_for` columns, each a CSV
     * of user-category codes). Returns:
     *   ['mandatory' => [...docs], 'optional' => [...docs]]
     * A document listed as mandatory takes precedence over optional.
     */
    public function docsForCategory(string $category, ?string $region = null): array
    {
        $out = ['mandatory' => [], 'optional' => []];
        $category = trim($category);
        if ($category === '') {
            return $out;
        }
        // Make the document set territory-aware (Aadhaar/PAN = India only, ID
        // proof / income-tax registration = international) the first time it is used.
        $this->ensureRegionDocs();
        $region = $this->normalizeRegion($region); // 'IN' | 'INTL' | null (no filter)
        try {
            $hasMand = $this->hasCol('doc_types', 'mandatory_for');
            $hasOpt  = $this->hasCol('doc_types', 'optional_for');
            if (!$hasMand && !$hasOpt) {
                return $out;
            }
            $hasRegion = $this->hasCol('doc_types', 'region');
            $rows = DB::table('doc_types')
                ->where('is_active', 1)
                ->when($this->hasCol('doc_types', 'deleted_at'), fn ($q) => $q->whereNull('deleted_at'))
                ->orderBy('name')
                ->get();
            foreach ($rows as $r) {
                // Territory filter: a doc tagged IN or INTL applies only in that
                // region; blank / ALL applies everywhere. When no region is passed
                // the filter is skipped (back-compatible).
                if ($region !== null && $hasRegion) {
                    $docRegion = strtoupper(trim((string) ($r->region ?? '')));
                    if ($docRegion !== '' && $docRegion !== 'ALL' && $docRegion !== $region) {
                        continue;
                    }
                }
                $doc = [
                    'code'       => $r->code ?? null,
                    'name'       => $r->name ?? null,
                    'file_types' => $r->file_types ?? null,
                ];
                $mand = $hasMand ? $this->csvList($r->mandatory_for ?? '') : [];
                $opt  = $hasOpt ? $this->csvList($r->optional_for ?? '') : [];
                if (in_array($category, $mand, true)) {
                    $out['mandatory'][] = $doc;
                } elseif (in_array($category, $opt, true)) {
                    $out['optional'][] = $doc;
                }
            }
        } catch (Throwable) {
            // fall through with whatever was gathered
        }
        return $out;
    }

    /** Normalize a currency/region hint to 'IN' | 'INTL' | null (no filter). */
    private function normalizeRegion(?string $region): ?string
    {
        if ($region === null) {
            return null;
        }
        $r = strtoupper(trim($region));
        if ($r === '') {
            return null;
        }
        if (in_array($r, ['IN', 'INR', 'INDIA', 'DOMESTIC'], true)) {
            return 'IN';
        }
        if (in_array($r, ['INTL', 'USD', 'INTERNATIONAL', 'FOREIGN'], true)) {
            return 'INTL';
        }
        return null;
    }

    /** Applicant territory for KYC: 'IN' (India / INR) or 'INTL' (international / USD). */
    public function regionForUser($user): string
    {
        try {
            $cur = \App\Services\Pricing\CurrencyResolver::lockedCurrencyFor($user);
            if ($cur === null) {
                $cur = strtoupper((string) ($user->currency ?? '')) ?: 'INR';
            }
            return $cur === 'USD' ? 'INTL' : 'IN';
        } catch (\Throwable) {
            return 'IN';
        }
    }

    private static bool $regionDocsEnsured = false;

    /**
     * One-time, marker-gated reconciliation that makes the KYC document set
     * territory-aware and scopes business documents to franchise/partner roles:
     *   - Aadhaar / PAN                         -> India only        (region 'IN')
     *   - Valid ID Proof / Income Tax Reg.      -> International only (region 'INTL')
     *   - Business / GST / tax-registration     -> required for FRANCHISE roles only
     * It reads the live `doc_types` and `user_categories`, so it adapts to whatever
     * codes the admin uses, and is gated by a Setting marker so later admin edits in
     * the Doc Types master are never overwritten.
     */
    private function ensureRegionDocs(): void
    {
        if (self::$regionDocsEnsured) {
            return;
        }
        self::$regionDocsEnsured = true;
        try {
            if (!\Illuminate\Support\Facades\Schema::hasTable('doc_types')) {
                return;
            }
            // 1) Ensure the `region` column exists.
            if (!$this->hasCol('doc_types', 'region')) {
                try {
                    DB::statement("ALTER TABLE `doc_types` ADD COLUMN `region` VARCHAR(8) NULL DEFAULT NULL");
                } catch (Throwable) {
                }
            }
            if (!$this->hasCol('doc_types', 'region')) {
                return;
            }

            // Marker gate (matches GeoSeeder's convention).
            if ((string) \App\Models\Setting::get('kyc_region_docs_v1', '') === 'done') {
                return;
            }

            // 2) Resolve category sets from the live master.
            $franchiseRoles = [];
            $allUpgradeRoles = [];
            try {
                $cats = DB::table('user_categories')
                    ->when($this->hasCol('user_categories', 'is_active'), fn ($q) => $q->where('is_active', 1))
                    ->get();
                foreach ($cats as $c) {
                    $code = (string) ($c->code ?? '');
                    if ($code === '' || in_array(strtolower($code), ['client', 'user', 'usr', 'yajman', 'yajmaan'], true)) {
                        continue;
                    }
                    $allUpgradeRoles[] = $code;
                    if (preg_match('/franc|partner|fpr|prabandh/i', $code . ' ' . (string) ($c->name ?? ''))) {
                        $franchiseRoles[] = $code;
                    }
                }
            } catch (Throwable) {
            }
            $allUpgradeRoles = array_values(array_unique($allUpgradeRoles));
            $franchiseRoles  = array_values(array_unique($franchiseRoles));

            // 3) Classify existing rows -> tag region, scope business docs.
            $haveIdProofIntl = false;
            $haveIncomeTaxIntl = false;
            $aadhaarMandatory = null;
            $panMandatory = null;
            try {
                foreach (DB::table('doc_types')->get() as $r) {
                    $hay = strtolower(((string) ($r->name ?? '')) . ' ' . ((string) ($r->code ?? '')));
                    $set = [];
                    if (preg_match('/aadha/', $hay)) {
                        $set['region'] = 'IN';
                        $aadhaarMandatory = (string) ($r->mandatory_for ?? '');
                    } elseif (preg_match('/\bpan\b|pancard|pan.?card/', $hay)) {
                        $set['region'] = 'IN';
                        $panMandatory = (string) ($r->mandatory_for ?? '');
                    } elseif (preg_match('/id ?proof|valid ?id|passport|national ?id|identity/', $hay)) {
                        $set['region'] = 'INTL';
                        $haveIdProofIntl = true;
                    } elseif (preg_match('/income ?tax|\bitr\b|tax ?id|\btin\b|\bssn\b/', $hay)) {
                        $set['region'] = 'INTL';
                        $haveIncomeTaxIntl = true;
                    } elseif (preg_match('/business|gst|tax ?reg|incorporat|company ?reg/', $hay)) {
                        $set['region'] = '';
                        if ($this->hasCol('doc_types', 'mandatory_for') && !empty($franchiseRoles)) {
                            $set['mandatory_for'] = implode(',', $franchiseRoles);
                        }
                    }
                    if (!empty($set)) {
                        try {
                            DB::table('doc_types')->where('id', $r->id)->update($set);
                        } catch (Throwable) {
                        }
                    }
                }
            } catch (Throwable) {
            }

            // 4) Create the international personal documents if none exist yet,
            //    mirroring the applicability of the Indian equivalents.
            $mkMandatory = function (?string $mirror) use ($allUpgradeRoles): string {
                $m = trim((string) $mirror);
                return $m !== '' ? $m : implode(',', $allUpgradeRoles);
            };
            $insertDoc = function (string $code, string $name, string $region, string $mandatory): void {
                try {
                    if (DB::table('doc_types')->where('code', $code)->exists()) {
                        $upd = ['region' => $region, 'is_active' => 1];
                        if ($this->hasCol('doc_types', 'mandatory_for')) {
                            $upd['mandatory_for'] = $mandatory;
                        }
                        DB::table('doc_types')->where('code', $code)->update($upd);
                        return;
                    }
                    $row = ['code' => $code, 'name' => $name, 'region' => $region, 'is_active' => 1];
                    if ($this->hasCol('doc_types', 'mandatory_for')) {
                        $row['mandatory_for'] = $mandatory;
                    }
                    if ($this->hasCol('doc_types', 'optional_for')) {
                        $row['optional_for'] = '';
                    }
                    if ($this->hasCol('doc_types', 'file_types')) {
                        $row['file_types'] = 'jpg,jpeg,png,pdf,webp';
                    }
                    if ($this->hasCol('doc_types', 'created_at')) {
                        $row['created_at'] = now();
                    }
                    if ($this->hasCol('doc_types', 'updated_at')) {
                        $row['updated_at'] = now();
                    }
                    DB::table('doc_types')->insert($row);
                } catch (Throwable) {
                }
            };
            if (!$haveIdProofIntl) {
                $insertDoc('intl_id_proof', 'Valid ID Proof (Passport / National ID)', 'INTL', $mkMandatory($aadhaarMandatory));
            }
            if (!$haveIncomeTaxIntl) {
                $insertDoc('intl_income_tax', 'Income Tax Registration', 'INTL', $mkMandatory($panMandatory));
            }

            // 5) Mark done so later admin edits are never overwritten.
            try {
                \App\Models\Setting::put('kyc_region_docs_v1', 'done', 'system');
            } catch (Throwable) {
            }
        } catch (Throwable) {
        }
    }

    /**
     * Required document types for a category/role. Prefers the new
     * `mandatory_for` multiselect; falls back to the legacy `required_for`
     * column on older tables. Returns the mandatory doc list only.
     */
    public function requiredTypes(string $role, ?string $region = null): array
    {
        if ($this->hasCol('doc_types', 'mandatory_for')) {
            return $this->docsForCategory($role, $region)['mandatory'];
        }
        // Legacy fallback (pre-multiselect tables still carrying required_for).
        try {
            return DB::table('doc_types')
                ->where('is_active', 1)
                ->when($this->hasCol('doc_types', 'deleted_at'), fn ($q) => $q->whereNull('deleted_at'))
                ->when($this->hasCol('doc_types', 'required_for'), fn ($q) => $q->where(function ($w) use ($role) {
                    $w->where('required_for', $role)
                        ->orWhere('required_for', 'both')
                        ->orWhereNull('required_for');
                }))
                ->orderBy('name')
                ->get()
                ->map(fn ($r) => (array) $r)
                ->all();
        } catch (Throwable) {
            return [];
        }
    }

    /** Split a CSV string into a trimmed, non-empty list. */
    private function csvList($v): array
    {
        return array_values(array_filter(array_map('trim', explode(',', (string) $v))));
    }

    /** Upload a KYC document for a user. Returns the created row (doc_number decrypted). */
    public function upload(int $userId, string $role, string $docType, string $docNumber, UploadedFile $file): array
    {
        $disk = config('kyc.disk', 'public');
        $path = $file->store(config('kyc.folder', 'kyc'), $disk);
        $fileUrl = Storage::disk($disk)->url($path);

        $doc = new UserDocument();
        $doc->user_id = $userId;
        $doc->role = $role;
        $doc->doc_type = $docType;
        $doc->doc_number = $docNumber !== '' ? $docNumber : null; // encrypted by the model
        $doc->file_url = $fileUrl;
        $doc->status = 'pending';
        $doc->save();

        Audit::log('kyc', $doc->id, 'upload', null, ['user' => $userId, 'type' => $docType]);

        return $this->present($doc->fresh());
    }

    /** A user's own documents (doc_number decrypted for the owner). */
    public function forUser(int $userId): array
    {
        return UserDocument::query()
            ->where('user_id', $userId)
            ->orderByDesc('id')
            ->get()
            ->map(fn ($d) => $this->present($d))
            ->all();
    }

    /** Admin review queue (optionally filtered by status/role), with user names. */
    public function queue(?string $status = 'pending', ?string $role = null): array
    {
        return UserDocument::query()
            ->when($status, fn ($q) => $q->where('status', $status))
            ->when($role, fn ($q) => $q->where('role', $role))
            ->orderByDesc('id')
            ->limit(200)
            ->get()
            ->map(function ($d) {
                $row = $this->present($d);
                $u = DB::table('users')->where('id', $d->user_id)->first(['name', 'email', 'phone']);
                $row['user_name'] = $u->name ?? null;
                $row['user_email'] = $u->email ?? null;
                $row['user_phone'] = $u->phone ?? null;
                return $row;
            })
            ->all();
    }

    /** Verify / reject / reset a document. */
    public function review(int $id, string $decision, string $notes = ''): bool
    {
        $decision = in_array($decision, config('kyc.statuses', ['pending', 'verified', 'rejected']), true) ? $decision : 'pending';
        $doc = UserDocument::find($id);
        if (!$doc) {
            return false;
        }
        $doc->status = $decision;
        $doc->notes = $notes !== '' ? $notes : null;
        $doc->reviewed_by = auth()->id();
        $doc->reviewed_at = now();
        $doc->save();

        Audit::log('kyc', $id, $decision, null, ['notes' => $notes]);
        return true;
    }

    /**
     * Overall KYC status for a user: 'verified' only if ≥1 doc and none pending/rejected;
     * 'pending' / 'rejected' short-circuit; 'none' if no documents.
     */
    public function statusForUser(int $userId): string
    {
        $statuses = UserDocument::query()->where('user_id', $userId)->pluck('status');
        if ($statuses->isEmpty()) {
            return 'none';
        }
        $hasVerified = false;
        foreach ($statuses as $s) {
            if ($s === 'verified') {
                $hasVerified = true;
            }
            if ($s === 'pending') {
                return 'pending';
            }
            if ($s === 'rejected') {
                return 'rejected';
            }
        }
        return $hasVerified ? 'verified' : 'pending';
    }

    /** Shape a document row for the API (doc_number already decrypted by the model). */
    private function present(UserDocument $d): array
    {
        return [
            'id'          => $d->id,
            'user_id'     => $d->user_id,
            'role'        => $d->role,
            'doc_type'    => $d->doc_type,
            'doc_number'  => $d->doc_number,
            'file_url'    => $d->file_url,
            'status'      => $d->status,
            'notes'       => $d->notes,
            'reviewed_by' => $d->reviewed_by,
            'reviewed_at' => $d->reviewed_at,
            'created_at'  => $d->created_at,
        ];
    }

    private function hasCol(string $table, string $col): bool
    {
        try {
            return in_array($col, \Illuminate\Support\Facades\Schema::getColumnListing($table), true);
        } catch (Throwable) {
            return false;
        }
    }
}
