<?php

namespace App\Services;

use App\Models\Branch;
use App\Models\Product;
use App\Models\ProductStock;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class BranchService
{
    /**
     * Transactional tables that carry a branch_id column.
     * When the very first branch is created we migrate all NULL-branch records
     * to belong to that branch so they remain visible.
     */
    private const BRANCHED_TABLES = [
        'categories',
        'products',
        'sales',
        'sale_items',
        'payments',
        'purchases',
        'purchase_items',
        'expenses',
        'inventory_adjustments',
        'loyalty_transactions',
        'customer_credit_transactions',
        'returns',
        'return_items',
    ];

    /**
     * Create a new branch.
     * If this is the FIRST branch for the organisation:
     *   - marks it as default
     *   - migrates all existing null-branch records (products, categories,
     *     transactions, etc.) to it so nothing disappears
     *   - seeds ProductStock with real current stock quantities (not 0)
     * Additional branches start with an empty catalog; staff add products
     * for that branch independently.
     */
    public function create(array $data): Branch
    {
        return DB::transaction(function () use ($data) {
            $data['code'] = strtoupper(Str::slug($data['code'] ?? $data['name'], '-'));

            $orgId = $data['organization_id'] ?? app('current_organization_id');
            $isFirst = Branch::withoutOrganizationScope()
                ->where('organization_id', $orgId)
                ->doesntExist();

            $branch = Branch::create(array_merge($data, [
                'is_default'      => $isFirst,
                'organization_id' => $orgId,
            ]));

            if ($isFirst) {
                // Migrate all pre-branch data to this branch
                $this->migrateUnbranchedData($orgId, $branch->id);
                // Seed product_stocks using REAL current quantities
                $this->seedProductStocksForFirstBranch($branch);
            }
            // For branch 2+: no auto-seeding; each branch manages its own catalog

            return $branch;
        });
    }

    public function update(Branch $branch, array $data): Branch
    {
        $branch->update($data);
        return $branch->fresh();
    }

    public function delete(Branch $branch): void
    {
        if ($branch->is_default) {
            throw new \Exception('Cannot delete the default branch.');
        }

        $hasSales     = $branch->sales()->exists();
        $hasPurchases = $branch->purchases()->exists();

        if ($hasSales || $hasPurchases) {
            throw new \Exception(
                'Cannot delete a branch that has sales or purchase records. Deactivate it instead.'
            );
        }

        DB::transaction(function () use ($branch) {
            ProductStock::where('branch_id', $branch->id)->delete();
            $branch->delete();
        });
    }

    /**
     * Set default branch for an organisation.
     */
    public function setDefault(Branch $branch): Branch
    {
        DB::transaction(function () use ($branch) {
            Branch::withoutOrganizationScope()
                ->where('organization_id', $branch->organization_id)
                ->update(['is_default' => false]);

            $branch->update(['is_default' => true]);
        });

        return $branch->fresh();
    }

    /**
     * Migrate all transactional records with branch_id = NULL for this org
     * to the given branch. Called only when the first branch is created so that
     * pre-existing data is not lost.
     */
    private function migrateUnbranchedData(int $orgId, int $branchId): void
    {
        foreach (self::BRANCHED_TABLES as $table) {
            if (!DB::getSchemaBuilder()->hasColumn($table, 'branch_id')) {
                continue;
            }
            if (!DB::getSchemaBuilder()->hasColumn($table, 'organization_id')) {
                continue;
            }
            DB::table($table)
                ->where('organization_id', $orgId)
                ->whereNull('branch_id')
                ->update(['branch_id' => $branchId]);
        }
    }

    /**
     * Seed ProductStock rows for the FIRST branch using the real stock quantities
     * already stored in products.stock_quantity, so nothing disappears.
     */
    private function seedProductStocksForFirstBranch(Branch $branch): void
    {
        $orgId = $branch->organization_id;

        $products = Product::withoutOrganizationScope()
            ->where('organization_id', $orgId)
            ->get(['id', 'stock_quantity']);

        $inserts = $products->map(fn($p) => [
            'organization_id' => $orgId,
            'product_id'      => $p->id,
            'branch_id'       => $branch->id,
            'quantity'        => max(0, (int) $p->stock_quantity),
            'created_at'      => now(),
            'updated_at'      => now(),
        ])->all();

        foreach (array_chunk($inserts, 200) as $chunk) {
            // Insert; if a row already exists (e.g. from a previous failed attempt), update it
            DB::table('product_stocks')->upsert(
                $chunk,
                ['product_id', 'branch_id'],
                ['quantity', 'updated_at']
            );
        }
    }

    /**
     * Seed ProductStock rows (qty = 0) for each product in the organisation.
     * Used for 2nd+ branches.
     */
    public function seedProductStocksForBranch(Branch $branch): void
    {
        $orgId = $branch->organization_id;

        $productIds = Product::withoutOrganizationScope()
            ->where('organization_id', $orgId)
            ->pluck('id');

        $inserts = $productIds->map(fn($pid) => [
            'organization_id' => $orgId,
            'product_id'      => $pid,
            'branch_id'       => $branch->id,
            'quantity'        => 0,
            'created_at'      => now(),
            'updated_at'      => now(),
        ])->all();

        foreach (array_chunk($inserts, 200) as $chunk) {
            ProductStock::insertOrIgnore($chunk);
        }
    }

    /**
     * Get aggregate stock snapshot across branches for an organisation.
     */
    public function getStockSnapshot(int $orgId): array
    {
        return ProductStock::withoutGlobalScope('branch')
            ->where('organization_id', $orgId)
            ->with(['product:id,name,sku', 'branch:id,name,code'])
            ->get()
            ->groupBy('branch_id')
            ->map(fn($rows) => $rows->keyBy('product_id')->map->quantity)
            ->toArray();
    }
}

