<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\AccurateCodeMapping;
use App\Models\AccurateExportBatch;
use App\Models\AccurateExportItem;
use App\Models\Purchase;
use App\Models\Sale;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class AccurateExportController extends Controller
{
    // ─── Code Mappings ────────────────────────────────────────────

    /**
     * GET /api/accurate-mappings
     */
    public function mappingIndex(Request $request)
    {
        $mappings = AccurateCodeMapping::query()
            ->when($request->entity_type, fn($q) => $q->where('entity_type', $request->entity_type))
            ->when($request->search, fn($q) => $q
                ->where('source_code',  'like', "%{$request->search}%")
                ->orWhere('source_label',  'like', "%{$request->search}%")
                ->orWhere('accurate_code', 'like', "%{$request->search}%")
                ->orWhere('accurate_name', 'like', "%{$request->search}%"))
            ->orderBy('entity_type')
            ->orderBy('source_code')
            ->get();

        return response()->json($mappings);
    }

    /**
     * POST /api/accurate-mappings
     */
    public function mappingStore(Request $request)
    {
        $data = $request->validate([
            'entity_type'   => ['required', 'in:product,customer,supplier'],
            'source_code'   => ['required', 'string', 'max:100'],
            'source_label'  => ['nullable', 'string', 'max:255'],
            'accurate_code' => ['required', 'string', 'max:100'],
            'accurate_name' => ['nullable', 'string', 'max:255'],
            'notes'         => ['nullable', 'string', 'max:255'],
        ]);

        $mapping = AccurateCodeMapping::updateOrCreate(
            ['entity_type' => $data['entity_type'], 'source_code' => $data['source_code']],
            $data
        );

        return response()->json($mapping, 201);
    }

    /**
     * PUT /api/accurate-mappings/{id}
     */
    public function mappingUpdate(Request $request, AccurateCodeMapping $accurateMapping)
    {
        $data = $request->validate([
            'source_label'  => ['nullable', 'string', 'max:255'],
            'accurate_code' => ['required', 'string', 'max:100'],
            'accurate_name' => ['nullable', 'string', 'max:255'],
            'notes'         => ['nullable', 'string', 'max:255'],
        ]);

        $accurateMapping->update($data);

        return response()->json($accurateMapping);
    }

    /**
     * DELETE /api/accurate-mappings/{id}
     */
    public function mappingDestroy(AccurateCodeMapping $accurateMapping)
    {
        $accurateMapping->delete();

        return response()->json(null, 204);
    }

    // ─── Export Preview ───────────────────────────────────────────

    /**
     * GET /api/accurate-export/preview
     */
    public function preview(Request $request)
    {
        $request->validate([
            'type'      => ['required', 'in:sales,sales_order,delivery_order,purchases'],
            'date_from' => ['required', 'date'],
            'date_to'   => ['required', 'date', 'after_or_equal:date_from'],
        ]);

        $mappings = AccurateCodeMapping::all()->keyBy(fn($m) => strtolower($m->entity_type . ':' . $m->source_code));

        $exportableType = match($request->type) {
            'sales'          => 'sale',
            'sales_order'    => 'sale_so',
            'delivery_order' => 'sale_do',
            default          => 'purchase',
        };

        $alreadyExported = AccurateExportItem::query()
            ->where('exportable_type', $exportableType)
            ->pluck('exportable_id')
            ->flip();

        if (in_array($request->type, ['sales', 'sales_order', 'delivery_order'])) {
            $withRelations = in_array($request->type, ['sales_order', 'delivery_order'])
                ? ['user:id,name', 'items.product:id,sku,unit,category_id', 'items.product.category:id,name', 'warehouse:id,code,name']
                : ['user:id,name', 'items', 'warehouse:id,code,name'];
            $records = Sale::with($withRelations)
                ->whereDate('created_at', '>=', $request->date_from)
                ->whereDate('created_at', '<=', $request->date_to)
                ->whereNull('voided_at')
                ->latest()
                ->get()
                ->map(fn($s) => $this->formatSalePreview($s, $mappings, $alreadyExported));
        } else {
            $records = Purchase::with(['user:id,name', 'items', 'warehouse:id,code,name'])
                ->whereDate('created_at', '>=', $request->date_from)
                ->whereDate('created_at', '<=', $request->date_to)
                ->latest()
                ->get()
                ->map(fn($p) => $this->formatPurchasePreview($p, $mappings, $alreadyExported));
        }

        return response()->json(['data' => $records]);
    }

    // ─── Export Batches ───────────────────────────────────────────

    /**
     * GET /api/accurate-export/batches
     */
    public function batchIndex()
    {
        $batches = AccurateExportBatch::with('exportedBy:id,name')
            ->latest()
            ->get()
            ->map(fn($b) => [
                'id'           => $b->id,
                'type'         => $b->type,
                'date_from'    => $b->date_from->toDateString(),
                'date_to'      => $b->date_to->toDateString(),
                'file_name'    => $b->file_name,
                'record_count' => $b->record_count,
                'exported_by'  => $b->exportedBy?->name ?? 'Unknown',
                'created_at'   => $b->created_at->toISOString(),
            ]);

        return response()->json($batches);
    }

    /**
     * GET /api/accurate-export/batches/{id}
     */
    public function batchShow(AccurateExportBatch $batch)
    {
        $batch->load(['exportedBy:id,name', 'items']);

        $mappings = AccurateCodeMapping::all()->keyBy(fn($m) => strtolower($m->entity_type . ':' . $m->source_code));

        if (in_array($batch->type, ['sales', 'sales_order', 'delivery_order'])) {
            $ids        = $batch->items->pluck('exportable_id');
            $relations  = in_array($batch->type, ['sales_order', 'delivery_order'])
                ? ['items.product:id,sku,unit,category_id', 'items.product.category:id,name', 'warehouse:id,code,name']
                : ['items', 'warehouse:id,code,name'];
            $models     = Sale::with($relations)->whereIn('id', $ids)->get()->keyBy('id');
            $buildFn    = match($batch->type) {
                'sales_order'    => fn($m) => $this->buildSalesOrderExportRow($m, $mappings),
                'delivery_order' => fn($m) => $this->buildDeliveryOrderExportRow($m, $mappings),
                default          => fn($m) => $this->buildSaleExportRow($m, $mappings),
            };
            $rows       = $batch->items->map(fn($item) => isset($models[$item->exportable_id])
                ? $buildFn($models[$item->exportable_id])
                : null)->filter()->values();
        } else {
            $ids    = $batch->items->pluck('exportable_id');
            $models = Purchase::with(['items', 'warehouse:id,code,name'])->whereIn('id', $ids)->get()->keyBy('id');
            $rows   = $batch->items->map(fn($item) => isset($models[$item->exportable_id])
                ? $this->buildPurchaseExportRow($models[$item->exportable_id], $mappings)
                : null)->filter()->values();
        }

        return response()->json([
            'id'           => $batch->id,
            'type'         => $batch->type,
            'date_from'    => $batch->date_from->toDateString(),
            'date_to'      => $batch->date_to->toDateString(),
            'file_name'    => $batch->file_name,
            'record_count' => $batch->record_count,
            'exported_by'  => $batch->exportedBy?->name ?? 'Unknown',
            'created_at'   => $batch->created_at->toISOString(),
            'rows'         => $rows,
        ]);
    }

    /**
     * POST /api/accurate-export/batches
     * Records the batch and returns structured data for client-side Excel generation.
     */
    public function batchStore(Request $request)
    {
        $data = $request->validate([
            'type'       => ['required', 'in:sales,sales_order,delivery_order,purchases'],
            'date_from'  => ['required', 'date'],
            'date_to'    => ['required', 'date', 'after_or_equal:date_from'],
            'ids'        => ['required', 'array', 'min:1'],
            'ids.*'      => ['required', 'integer'],
            'file_name'  => ['nullable', 'string', 'max:255'],
        ]);

        $mappings = AccurateCodeMapping::all()->keyBy(fn($m) => strtolower($m->entity_type . ':' . $m->source_code));

        return DB::transaction(function () use ($data, $mappings, $request) {
            if ($data['type'] === 'sales') {
                $models = Sale::with(['items', 'warehouse:id,code,name'])
                    ->whereIn('id', $data['ids'])
                    ->whereNull('voided_at')
                    ->get();
            } elseif (in_array($data['type'], ['sales_order', 'delivery_order'])) {
                $models = Sale::with(['items.product:id,sku,unit,category_id', 'items.product.category:id,name', 'warehouse:id,code,name'])
                    ->whereIn('id', $data['ids'])
                    ->whereNull('voided_at')
                    ->get();
            } else {
                $models = Purchase::with(['items', 'warehouse:id,code,name'])
                    ->whereIn('id', $data['ids'])
                    ->get();
            }

            $batch = AccurateExportBatch::create([
                'type'         => $data['type'],
                'date_from'    => $data['date_from'],
                'date_to'      => $data['date_to'],
                'file_name'    => $data['file_name'] ?? null,
                'record_count' => $models->count(),
                'exported_by'  => $request->user()->id,
            ]);

            $exportableType = match($data['type']) {
                'sales'          => 'sale',
                'sales_order'    => 'sale_so',
                'delivery_order' => 'sale_do',
                default          => 'purchase',
            };
            $referenceField = $data['type'] === 'purchases' ? 'po_number' : 'order_no';

            $items = $models->map(fn($m) => [
                'batch_id'        => $batch->id,
                'exportable_type' => $exportableType,
                'exportable_id'   => $m->id,
                'reference_no'    => $m->{$referenceField},
                'created_at'      => now(),
                'updated_at'      => now(),
            ])->toArray();

            AccurateExportItem::insert($items);

            $rows = match($data['type']) {
                'sales'          => $models->map(fn($m) => $this->buildSaleExportRow($m, $mappings)),
                'sales_order'    => $models->map(fn($m) => $this->buildSalesOrderExportRow($m, $mappings)),
                'delivery_order' => $models->map(fn($m) => $this->buildDeliveryOrderExportRow($m, $mappings)),
                default          => $models->map(fn($m) => $this->buildPurchaseExportRow($m, $mappings)),
            };

            return response()->json([
                'batch_id'  => $batch->id,
                'type'      => $batch->type,
                'file_name' => $batch->file_name,
                'rows'      => $rows->values(),
            ], 201);
        });
    }

    // ─── Private helpers ──────────────────────────────────────────

    /**
     * Find channel mapping with fallback normalization.
     * Handles mismatch between old enum values (vibe24jam) and display names (Vibe 24 Jam).
     */
    private function findChannelMapping($mappings, string $channel): ?AccurateCodeMapping
    {
        // Exact match first
        if ($m = $mappings->get(strtolower('customer:' . $channel))) {
            return $m;
        }
        // Normalized fallback: strip non-alphanumeric and compare
        $normalized = preg_replace('/[^a-z0-9]/', '', strtolower($channel));
        return $mappings->first(fn($m) =>
            $m->entity_type === 'customer' &&
            preg_replace('/[^a-z0-9]/', '', strtolower($m->source_code)) === $normalized
        ) ?: null;
    }

    private function formatSalePreview($sale, $mappings, $alreadyExported): array
    {
        $channelMapping = $this->findChannelMapping($mappings, $sale->channel);

        $itemsStatus = $sale->items->map(function ($item) use ($mappings) {
            $mapped = $mappings->get(strtolower('product:' . $item->sku));
            return [
                'sku'           => $item->sku,
                'product_name'  => $item->product_name,
                'qty'           => $item->qty,
                'price'         => $item->price,
                'accurate_code' => $mapped?->accurate_code,
                'mapped'        => (bool) $mapped,
            ];
        });

        $allMapped = $channelMapping && $itemsStatus->every(fn($i) => $i['mapped']);

        return [
            'id'               => $sale->id,
            'reference_no'     => $sale->order_no,
            'date'             => $sale->created_at->toDateString(),
            'channel'          => $sale->channel,
            'customer_code'    => $channelMapping?->accurate_code,
            'warehouse_name'   => $sale->warehouse?->name ?? '-',
            'total'            => $sale->total,
            'items'            => $itemsStatus,
            'mapping_status'   => $allMapped ? 'ready' : 'incomplete',
            'already_exported' => isset($alreadyExported[$sale->id]),
        ];
    }

    private function formatPurchasePreview($purchase, $mappings, $alreadyExported): array
    {
        $supplierCode = $mappings->get(strtolower('supplier:' . ($purchase->supplier?->code ?? $purchase->supplier_name)));

        $itemsStatus = $purchase->items->map(function ($item) use ($mappings) {
            $mapped = $mappings->get(strtolower('product:' . $item->sku));
            return [
                'sku'          => $item->sku,
                'product_name' => $item->product_name,
                'qty_ordered'  => $item->qty_ordered,
                'unit_price'   => $item->unit_price,
                'accurate_code' => $mapped?->accurate_code,
                'mapped'       => (bool) $mapped,
            ];
        });

        $allMapped = $supplierCode && $itemsStatus->every(fn($i) => $i['mapped']);

        return [
            'id'               => $purchase->id,
            'reference_no'     => $purchase->po_number,
            'date'             => $purchase->created_at->toDateString(),
            'supplier_name'    => $purchase->supplier_name,
            'supplier_code'    => $supplierCode?->accurate_code,
            'warehouse_name'   => $purchase->warehouse?->name ?? '-',
            'status'           => $purchase->status,
            'total'            => $purchase->total,
            'items'            => $itemsStatus,
            'mapping_status'   => $allMapped ? 'ready' : 'incomplete',
            'already_exported' => isset($alreadyExported[$purchase->id]),
        ];
    }

    private function buildSaleExportRow($sale, $mappings): array
    {
        $channelMapping = $this->findChannelMapping($mappings, $sale->channel);

        return [
            'no_transaksi'    => $sale->order_no,
            'tanggal'         => $sale->created_at->format('Y-m-d'),
            'kode_customer'   => $channelMapping?->accurate_code ?? $sale->channel,
            'nama_customer'   => $channelMapping?->accurate_name ?? $sale->channel,
            'gudang'          => $sale->warehouse?->code ?? '',
            'metode_bayar'    => $sale->payment_method ?? '',
            'channel'         => $sale->channel,
            'keterangan'      => $sale->notes ?? '',
            'total'           => $sale->total,
            'items'           => $sale->items->map(fn($i) => [
                'no_transaksi'  => $sale->order_no,
                'kode_barang'   => $mappings->get(strtolower('product:' . $i->sku))?->accurate_code ?? $i->sku,
                'nama_barang'   => $mappings->get(strtolower('product:' . $i->sku))?->accurate_name ?? $i->product_name,
                'qty'           => $i->qty,
                'satuan'        => 'pcs',
                'harga_satuan'  => $i->price,
                'subtotal'      => $i->subtotal,
                'is_free'       => (bool) $i->is_free,
            ])->values()->toArray(),
        ];
    }

    private function resolveItemUnit($product): string
    {
        if (!$product) return 'pcs';

        $category = strtolower(trim($product->category?->name ?? ''));
        if ($category === 'mixer') return '';

        return $this->normalizeUnit($product->unit ?? 'pcs');
    }

    private function normalizeUnit(string $unit): string
    {
        return match (strtolower(trim($unit))) {
            'bot', 'botol', 'bottle', 'bottles' => 'btl',
            'pcs', 'piece', 'pieces', 'buah'    => 'pcs',
            'box', 'kotak', 'boks'              => 'box',
            'ctn', 'carton', 'karton', 'ktn'   => 'ctn',
            'kg', 'kilo', 'kilogram'            => 'kg',
            'ltr', 'liter', 'litre', 'lt'      => 'ltr',
            'pak', 'pack', 'pck'               => 'pak',
            'lusin', 'dozen', 'doz'            => 'doz',
            default                             => $unit,
        };
    }

    private function buildPurchaseExportRow($purchase, $mappings): array
    {
        $supplierKey     = $purchase->supplier?->code ?? $purchase->supplier_name;
        $supplierMapping = $mappings->get(strtolower('supplier:' . $supplierKey));

        return [
            'no_transaksi'    => $purchase->po_number,
            'tanggal'         => $purchase->created_at->format('Y-m-d'),
            'kode_supplier'   => $supplierMapping?->accurate_code ?? $supplierKey,
            'nama_supplier'   => $supplierMapping?->accurate_name ?? $purchase->supplier_name,
            'gudang'          => $purchase->warehouse?->code ?? '',
            'keterangan'      => $purchase->notes ?? '',
            'total'           => $purchase->total,
            'items'           => $purchase->items->map(fn($i) => [
                'no_transaksi'  => $purchase->po_number,
                'kode_barang'   => $mappings->get(strtolower('product:' . $i->sku))?->accurate_code ?? $i->sku,
                'nama_barang'   => $mappings->get(strtolower('product:' . $i->sku))?->accurate_name ?? $i->product_name,
                'qty'           => $i->qty_ordered,
                'satuan'        => 'pcs',
                'harga_satuan'  => $i->unit_price,
                'subtotal'      => $i->subtotal,
            ])->values()->toArray(),
        ];
    }

    private function buildSalesOrderExportRow($sale, $mappings): array
    {
        $channelMapping = $this->findChannelMapping($mappings, $sale->channel);

        return [
            'no_transaksi'  => $sale->order_no,
            'tanggal'       => $sale->created_at->format('Y-m-d'),
            'kode_customer' => $channelMapping?->accurate_code ?? $sale->channel,
            'nama_customer' => $channelMapping?->accurate_name ?? $sale->channel,
            'gudang'        => $sale->warehouse?->code ?? '',
            'metode_bayar'  => $sale->payment_method ?? '',
            'channel'       => $sale->channel,
            'keterangan'    => $sale->notes ?? '',
            'total'         => $sale->total,
            'items'         => $sale->items->map(fn($i) => [
                'no_transaksi' => $sale->order_no,
                'kode_barang'  => $mappings->get(strtolower('product:' . $i->sku))?->accurate_code ?? $i->sku,
                'nama_barang'  => $mappings->get(strtolower('product:' . $i->sku))?->accurate_name ?? $i->product_name,
                'qty'          => $i->qty,
                'satuan'       => $this->resolveItemUnit($i->product),
                'harga_satuan' => $i->price,
                'subtotal'     => $i->subtotal,
                'is_free'      => (bool) $i->is_free,
            ])->values()->toArray(),
        ];
    }

    private function buildDeliveryOrderExportRow($sale, $mappings): array
    {
        $channelMapping = $this->findChannelMapping($mappings, $sale->channel);

        return [
            'no_transaksi'  => $sale->order_no,
            'tanggal'       => $sale->created_at->format('Y-m-d'),
            'kode_customer' => $channelMapping?->accurate_code ?? $sale->channel,
            'nama_customer' => $channelMapping?->accurate_name ?? $sale->channel,
            'gudang'        => $sale->warehouse?->code ?? '',
            'channel'       => $sale->channel,
            'keterangan'    => $sale->notes ?? '',
            'total'         => $sale->total,
            'items'         => $sale->items->map(fn($i) => [
                'no_transaksi' => $sale->order_no,
                'kode_barang'  => $mappings->get(strtolower('product:' . $i->sku))?->accurate_code ?? $i->sku,
                'nama_barang'  => $mappings->get(strtolower('product:' . $i->sku))?->accurate_name ?? $i->product_name,
                'qty'          => $i->qty,
                'satuan'       => $this->resolveItemUnit($i->product),
                'harga_satuan' => $i->price,
                'subtotal'     => $i->subtotal,
                'is_free'      => (bool) $i->is_free,
            ])->values()->toArray(),
        ];
    }
}
