<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Admin\Concerns\HandlesCostOfSaleLink;
use App\Http\Controllers\Controller;
use App\Jobs\ScanAttachmentJob;
use App\Models\Attachment;
use App\Models\Currency;
use App\Models\Expense;
use App\Models\ExpenseCategory;
use App\Models\Vendor;
use App\Services\CsvExpenseImportService;
use App\Support\CostOfSale\CostOfSaleReference;
use App\Support\CostOfSale\LinkableResolver;
use App\Traits\LogsAdminActivity;
use App\Traits\ValidatesFileUpload;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;

// PRC-814 — Gider CRUD + fiş eki (polimorfik Attachment + ClamAV async scan).
class ExpenseController extends Controller
{
    use HandlesCostOfSaleLink; // PRC-816
    use LogsAdminActivity;
    use ValidatesFileUpload;

    public function index()
    {
        $currencies = Currency::all();
        $categories = ExpenseCategory::where('is_active', true)->orderBy('name')->get();
        $vendors = Vendor::where('is_active', true)->orderBy('name')->get();

        return view('admin.expenses.index', compact('currencies', 'categories', 'vendors'));
    }

    public function records(Request $request): JsonResponse
    {
        $totalRecords = Expense::count();

        $query = Expense::with(['currency', 'category', 'vendor', 'createdByAdmin', 'attachments', 'linkable'])
            ->select('expenses.*');

        if ($request->filled('search')) {
            $raw = $request->input('search');
            $search = is_array($raw) ? ($raw['value'] ?? '') : (string) $raw;
            if ($search !== '') {
                $query->where(function ($q) use ($search) {
                    $q->where('description', 'like', "%{$search}%")
                        ->orWhere('note', 'like', "%{$search}%")
                        ->orWhereHas('vendor', fn ($v) => $v->where('name', 'like', "%{$search}%"))
                        ->orWhereHas('category', fn ($c) => $c->where('name', 'like', "%{$search}%"));
                });
            }
        }

        if ($request->filled('category_id')) {
            $query->where('category_id', $request->input('category_id'));
        }
        if ($request->filled('vendor_id')) {
            $query->where('vendor_id', $request->input('vendor_id'));
        }
        if ($request->filled('currency_id')) {
            $query->where('currency_id', $request->input('currency_id'));
        }
        if ($request->filled('status')) {
            $query->where('status', $request->input('status'));
        }
        if ($request->filled('date_from')) {
            $query->whereDate('expense_date', '>=', $request->input('date_from'));
        }
        if ($request->filled('date_to')) {
            $query->whereDate('expense_date', '<=', $request->input('date_to'));
        }
        // PRC-816 — cost-of-sale bağ tipine göre filtrele (kısa key → sınıf).
        if ($request->filled('linkable_type')) {
            $class = LinkableResolver::classForKey($request->input('linkable_type'));
            $query->where('linkable_type', $class ?: '__none__');
        }

        $total = $query->count();

        $columns = ['id', 'expense_date', 'description', 'amount', 'category_id', 'vendor_id', 'status'];
        $orderColumn = (int) $request->input('order.0.column', 1);
        $orderDir = $request->input('order.0.dir', 'desc');
        $query->orderBy($columns[$orderColumn] ?? 'expense_date', $orderDir);

        $start = (int) $request->input('start', 0);
        $length = (int) $request->input('length', 10);

        $expenses = $query->skip($start)->take($length)->get();

        $data = $expenses->map(function (Expense $e) {
            // Yalnız taraması temiz fiş indirilebilir (scanPassed). Kirli/başarısız
            // gizli; henüz taranmamış (pending) için "taranıyor" göstergesi.
            $downloadable = $e->attachments->first(fn (Attachment $a) => $a->scanPassed());
            $pending = $e->attachments->contains(fn (Attachment $a) => $a->scan_status === 'pending');

            return [
                'id' => $e->id,
                'expense_date' => $e->expense_date?->format('d.m.Y'),
                'description' => $e->description,
                'amount' => number_format((float) $e->amount, 2),
                'currency_code' => $e->currency?->code ?? '-',
                'category' => $e->category?->name,
                'vendor' => $e->vendor?->name,
                'status' => $e->status,
                'receipt_id' => $downloadable?->id,
                'receipt_pending' => $downloadable ? false : $pending,
                'linked_label' => $e->linkable_type ? LinkableResolver::label($e->linkable) : null,
                'created_by_admin' => $e->createdByAdmin?->name,
            ];
        });

        return response()->json([
            'draw' => $request->input('draw'),
            'recordsTotal' => $totalRecords,
            'recordsFiltered' => $total,
            'data' => $data,
        ]);
    }

    public function create()
    {
        return view('admin.expenses.create', $this->formData());
    }

    public function store(Request $request): JsonResponse
    {
        $validated = $this->validateExpense($request);
        $link = $this->resolveLinkableInput($request); // PRC-816 (422 on invalid)

        try {
            $expense = DB::transaction(function () use ($request, $validated, $link) {
                $expense = Expense::create([
                    'amount' => $validated['amount'],
                    'currency_id' => $validated['currency_id'],
                    'category_id' => $validated['category_id'] ?? null,
                    'vendor_id' => $validated['vendor_id'] ?? null,
                    'expense_date' => $validated['expense_date'],
                    'description' => $validated['description'],
                    'note' => $validated['note'] ?? null,
                    'status' => $validated['status'],
                    'linkable_type' => $link['linkable_type'],
                    'linkable_id' => $link['linkable_id'],
                    'created_by_admin_id' => auth()->id(),
                ]);

                if ($request->hasFile('receipt')) {
                    $this->storeReceipt($request->file('receipt'), $expense);
                }

                // Log BEFORE commit — no secret/PII in the description (PRC-814 AC3).
                $this->logCreated($expense, "Created expense #{$expense->id}");

                return $expense;
            });

            return response()->json([
                'success' => true,
                'message' => __('admin/expenses.messages_created'),
                'redirect' => route('admin.expenses.index'),
            ]);
        } catch (\RuntimeException $e) {
            // Fiş dosyası güvenlik doğrulaması reddetti — kullanıcıya net dön.
            return response()->json([
                'success' => false,
                'errors' => ['receipt' => [$e->getMessage()]],
                'message' => $e->getMessage(),
            ], 422);
        } catch (\Throwable $e) {
            \Log::error('Expense create failed', [
                'admin_id' => auth()->id(),
                'exception' => get_class($e),
                'message' => $e->getMessage(),
                'file' => $e->getFile().':'.$e->getLine(),
            ]);

            return response()->json([
                'success' => false,
                'message' => __('admin/expenses.messages_save_error'),
            ], 500);
        }
    }

    public function edit(Expense $expense)
    {
        $expense->load('attachments', 'linkable');

        return view('admin.expenses.edit', array_merge($this->formData(), compact('expense')));
    }

    public function update(Request $request, Expense $expense): JsonResponse
    {
        $validated = $this->validateExpense($request);
        $link = $this->resolveLinkableInput($request); // PRC-816 (422 on invalid)

        try {
            DB::transaction(function () use ($request, $expense, $validated, $link) {
                $expense->update([
                    'amount' => $validated['amount'],
                    'currency_id' => $validated['currency_id'],
                    'category_id' => $validated['category_id'] ?? null,
                    'vendor_id' => $validated['vendor_id'] ?? null,
                    'expense_date' => $validated['expense_date'],
                    'description' => $validated['description'],
                    'note' => $validated['note'] ?? null,
                    'status' => $validated['status'],
                    'linkable_type' => $link['linkable_type'],
                    'linkable_id' => $link['linkable_id'],
                ]);

                if ($request->hasFile('receipt')) {
                    $this->storeReceipt($request->file('receipt'), $expense);
                }

                $this->logUpdated($expense, "Updated expense #{$expense->id}");
            });

            return response()->json([
                'success' => true,
                'message' => __('admin/expenses.messages_updated'),
                'redirect' => route('admin.expenses.index'),
            ]);
        } catch (\RuntimeException $e) {
            return response()->json([
                'success' => false,
                'errors' => ['receipt' => [$e->getMessage()]],
                'message' => $e->getMessage(),
            ], 422);
        } catch (\Throwable $e) {
            \Log::error('Expense update failed', [
                'admin_id' => auth()->id(),
                'expense_id' => $expense->id,
                'exception' => get_class($e),
                'message' => $e->getMessage(),
            ]);

            return response()->json([
                'success' => false,
                'message' => __('admin/expenses.messages_save_error'),
            ], 500);
        }
    }

    public function destroy(Expense $expense): JsonResponse
    {
        try {
            DB::transaction(function () use ($expense) {
                foreach ($expense->attachments as $attachment) {
                    $this->deleteReceiptFile($attachment);
                    $attachment->delete();
                }

                $this->logDeleted($expense, "Deleted expense #{$expense->id}");
                $expense->delete();
            });

            return response()->json([
                'success' => true,
                'message' => __('admin/expenses.messages_deleted'),
            ]);
        } catch (\Throwable $e) {
            return response()->json([
                'success' => false,
                'message' => __('admin/expenses.messages_delete_error'),
            ], 500);
        }
    }

    /**
     * Fiş/belge indir — 'private' disk + scan-gate. Kirli/bekleyen tarama
     * durumundaki dosya servis edilmez (ticket ekleri ile aynı sözleşme).
     */
    public function downloadReceipt(Expense $expense, Attachment $attachment)
    {
        abort_unless(
            $attachment->uploadable_type === Expense::class && $attachment->uploadable_id === $expense->id,
            404
        );

        if (! $attachment->scanPassed()) {
            abort(404, __('admin/common.messages_file_not_found'));
        }

        if (! Storage::disk('private')->exists($attachment->file_path)) {
            abort(404, __('admin/common.messages_file_not_found'));
        }

        return Storage::disk('private')->download(
            $attachment->file_path,
            $attachment->original_name,
            ['Content-Type' => $attachment->mime_type]
        );
    }

    /**
     * PRC-816 — select2-ajax: bağlanabilir satış varlıklarını ara.
     * `type` kısa key (order/service/domain/product/tld), `q` arama metni.
     */
    public function linkableSearch(Request $request): JsonResponse
    {
        $type = (string) $request->input('type', '');
        $q = (string) $request->input('q', '');

        return response()->json([
            'results' => LinkableResolver::search($type, $q),
        ]);
    }

    /**
     * PRC-816 — bağlı varlık için KATALOG referans maliyeti (ServiceCost/
     * DomainCost). Yalnız okur; gidere kopyalanmaz. type+id ile çağrılır.
     */
    public function linkableReference(Request $request): JsonResponse
    {
        $class = LinkableResolver::classForKey((string) $request->input('type'));
        $id = $request->input('id');

        if (! $class || ! $id) {
            return response()->json(['reference' => null]);
        }

        $model = $class::query()->find($id);
        $ref = $model ? CostOfSaleReference::for($model) : null;

        if (! $ref) {
            return response()->json(['reference' => null]);
        }

        $code = $ref['currency_id'] ? optional(Currency::find($ref['currency_id']))->code : null;

        return response()->json(['reference' => [
            'amount' => number_format($ref['amount'], 2),
            'time' => $ref['time'],
            'currency_code' => $code,
        ]]);
    }

    /**
     * PRC-819 — CSV başlıklarını + örnek satırları döndür (eşleme UI'ı için).
     */
    public function importAnalyze(Request $request, CsvExpenseImportService $svc): JsonResponse
    {
        $request->validate(['csv' => ['required', 'file', 'mimes:csv,txt', 'max:1024']]);

        return response()->json(['success' => true] + $svc->analyze($request->file('csv')));
    }

    /**
     * PRC-819 — CSV'yi eşleme + varsayılanlara göre gidere aktar.
     * dry_run=1 → önizleme (persist yok), aksi halde içe aktarım.
     */
    public function import(Request $request, CsvExpenseImportService $svc): JsonResponse
    {
        $validated = $request->validate([
            'csv' => ['required', 'file', 'mimes:csv,txt', 'max:1024'],
            'currency_id' => ['required', 'integer', 'exists:currencies,id'],
            'status' => ['required', 'in:pending,paid'],
            'map_date' => ['required', 'string'],
            'map_amount' => ['required', 'string'],
            'map_description' => ['required', 'string'],
            'map_category' => ['nullable', 'string'],
            'map_vendor' => ['nullable', 'string'],
            'default_category_id' => ['nullable', 'integer', 'exists:expense_categories,id'],
            'default_vendor_id' => ['nullable', 'integer', 'exists:vendors,id'],
            'date_format' => ['nullable', 'string', 'max:20'],
        ]);

        $mapping = [
            'date' => $validated['map_date'],
            'amount' => $validated['map_amount'],
            'description' => $validated['map_description'],
            'category' => $validated['map_category'] ?? null,
            'vendor' => $validated['map_vendor'] ?? null,
        ];

        $options = [
            'currency_id' => (int) $validated['currency_id'],
            'status' => $validated['status'],
            'default_category_id' => $validated['default_category_id'] ?? null,
            'default_vendor_id' => $validated['default_vendor_id'] ?? null,
            'date_format' => $validated['date_format'] ?? null,
            'only_debits' => $request->boolean('only_debits'),
        ];

        $dryRun = $request->boolean('dry_run');

        $result = $svc->import($request->file('csv'), $mapping, $options, $dryRun);

        if (! $dryRun && $result['added'] > 0) {
            // Toplu import audit — secret/PII loglanmaz (yalnız sayaçlar).
            $this->logBulkOperation('import', 'Expense', [], "Imported {$result['added']} expenses via CSV", [
                'added' => $result['added'],
                'skipped' => $result['skipped'],
            ]);
        }

        return response()->json(['success' => true, 'dry_run' => $dryRun] + $result);
    }

    private function formData(): array
    {
        return [
            'currencies' => Currency::all(),
            'categories' => ExpenseCategory::where('is_active', true)->orderBy('name')->get(),
            'vendors' => Vendor::where('is_active', true)->orderBy('name')->get(),
            'linkableTypeKeys' => array_keys(LinkableResolver::TYPES),
        ];
    }

    private function validateExpense(Request $request): array
    {
        return $request->validate([
            'amount' => ['required', 'numeric', 'min:0'],
            'currency_id' => ['required', 'integer', 'exists:currencies,id'],
            'category_id' => ['nullable', 'integer', 'exists:expense_categories,id'],
            'vendor_id' => ['nullable', 'integer', 'exists:vendors,id'],
            'expense_date' => ['required', 'date'],
            'description' => ['required', 'string', 'max:255'],
            'note' => ['nullable', 'string', 'max:2000'],
            'status' => ['required', 'in:pending,paid'],
            'receipt' => ['nullable', 'file', 'max:10240'],
        ]);
    }

    /**
     * Fiş dosyasını güvenli şekilde 'private' disk'e yükle + async ClamAV
     * taraması tetikle (ScanAttachmentJob). Ticket ek deseninin birebiri.
     */
    private function storeReceipt($file, Expense $expense): void
    {
        $securityError = $this->validateUploadSecurity($file);
        if ($securityError) {
            throw new \RuntimeException($securityError);
        }

        $originalName = $file->getClientOriginalName();
        $extension = strtolower($file->getClientOriginalExtension());
        $secureFileName = uniqid('expense_').'_'.time().'.'.$extension;

        $directory = 'expenses/'.date('Y/m');
        $filePath = $file->storeAs($directory, $secureFileName, 'private');

        $attachment = Attachment::create([
            'original_name' => $originalName,
            'file_name' => $secureFileName,
            'file_path' => $filePath,
            'mime_type' => $file->getMimeType(),
            'file_size' => $file->getSize(),
            'uploadable_type' => Expense::class,
            'uploadable_id' => $expense->id,
            'upload_source' => 'admin',
            'uploaded_by' => auth()->id(),
            'scan_status' => 'pending',
        ]);

        ScanAttachmentJob::dispatch($attachment->id);
    }

    private function deleteReceiptFile(Attachment $attachment): void
    {
        foreach (['private', 'attachments', 'public'] as $disk) {
            if (Storage::disk($disk)->exists($attachment->file_path)) {
                Storage::disk($disk)->delete($attachment->file_path);
            }
        }
    }
}
