<?php

namespace Modules\Inventory\Services;

use Modules\Inventory\Repositories\CategoryRepository;
use Illuminate\Support\Facades\DB;
use Modules\Accounts\Enums\AccountPrefixEnum;
use Modules\Accounts\Services\AccountService;
use Modules\Core\Exceptions\BusinessException;
use Modules\Inventory\DTO\InventorySettingFilter;
use Modules\Inventory\Models\Category;
use Modules\Inventory\Enums\CategoryTypeEnum;

class CategoryService
{
    public function __construct(
        protected AccountService $accountService,
        protected CategoryRepository $categoryRepo
    ) {}

    public function list(InventorySettingFilter $filters)
    {
        $columns = ['id', 'name', 'code', 'category_type', 'has_stock', 'has_batch', 'has_expiry', 'status'];

        return $this->categoryRepo->all($filters, $columns);
    }


    public function lookUpList(InventorySettingFilter $filters)
    {
        $columns = ['id', 'name', 'code', 'category_type'];

        return $this->categoryRepo->all($filters, $columns);
    }

    public function show(int $id)
    {
        return $this->categoryRepo->findWithRelations(
            $id,
            [
                'inventoryDepartment:id,name',
                'assetAccount:id,account_code,account_name',
                'accumulatedDepAccount:id,account_code,account_name',
                'depExpenseAccount:id,account_code,account_name',
            ]
        );
    }

    public function store(array $data)
    {
        return DB::transaction(function () use ($data) {
            $data = $this->applyBusinessRules($data);

            $accountIds = $this->accountService->createCategoryAccounts(
                categoryName: $data['name'],
                categoryCode: $data['code'],
                categoryType: $data['category_type']
            );

            $category = $this->categoryRepo->create([
                ...$data,
                ...$accountIds,
            ]);

            DB::afterCommit(function () {
                $this->accountService->clearTreeCache();
            });

            return $category;
        });
    }

    private function applyBusinessRules(array $data): array
    {
        return match ($data['category_type']) {
            CategoryTypeEnum::INVENTORY->value => [
                ...$data,
                'has_stock' => true,
                'has_batch' => $data['has_batch'] ?? false,
                'has_expiry' => $data['has_expiry'] ?? false,
            ],

            CategoryTypeEnum::EXPENSE->value,
            CategoryTypeEnum::ASSET->value => [
                ...$data,
                'has_stock' => false,
                'has_batch' => false,
                'has_expiry' => false,
            ],

            default => $data,
        };
    }

    public function update(Category $category, array $data)
    {
        return DB::transaction(function () use ($category, $data) {
            $this->validateUpdate($category, $data);

            $data = $this->applyBusinessRules($data);
            $nameChanged = $category->name !== $data['name'];

            $category = $this->categoryRepo->update($category, $data);

            if ($category->category_type === CategoryTypeEnum::ASSET->value && $nameChanged) {
                $this->renameAssetCategoryAccounts($category);
            }

            DB::afterCommit(function () {
                $this->accountService->clearTreeCache();
            });

            return $category->fresh();
        });
    }

    private function validateUpdate(Category $category, array $data): void
    {
        if ($category->category_type !== $data['category_type']) {
            BusinessException::message('Category type cannot be changed after creation.');
        }

        if ($category->code !== $data['code']) {
            BusinessException::message('Category code cannot be changed after creation.');
        }

        if ($category->category_type === CategoryTypeEnum::ASSET->value) {
            if (
                !$category->asset_account_id ||
                !$category->accum_dep_account_id ||
                !$category->dep_expense_account_id
            ) {
                BusinessException::message('Asset category is missing required accounting accounts.');
            }
        }

        $hasProducts = $this->categoryRepo->hasProducts($category->id);

        if ($hasProducts) {
            if ($category->has_stock != ($data['has_stock'] ?? $category->has_stock)) {
                BusinessException::message('Cannot change stock setting after products exist.');
            }

            if ($category->has_batch != ($data['has_batch'] ?? $category->has_batch)) {
                BusinessException::message('Cannot change batch setting after products exist.');
            }

            if ($category->has_expiry != ($data['has_expiry'] ?? $category->has_expiry)) {
                BusinessException::message('Cannot change expiry setting after products exist.');
            }
        }
    }

    private function renameAssetCategoryAccounts(Category $category): void
    {
        if (!$category->asset_account_id) {
            return;
        }

        $assetName = AccountPrefixEnum::ASSET->value . " - {$category->code} - {$category->name}";
        $accDepName = AccountPrefixEnum::ACCUMULATED_DEPRECIATION->value . " - {$category->code} - {$category->name}";
        $depExpenseName = AccountPrefixEnum::DEPRECIATION_EXPENSE->value . " - {$category->code} - {$category->name}";

        $this->accountService->renameAccount(
            accountId: $category->asset_account_id,
            newName: $assetName
        );

        $this->accountService->renameAccount(
            accountId: $category->accum_dep_account_id,
            newName: $accDepName
        );

        $this->accountService->renameAccount(
            accountId: $category->dep_expense_account_id,
            newName: $depExpenseName
        );
    }

    public function syncAssetCategoryAccounts(Category $category): void
    {
        if ($category->category_type !== CategoryTypeEnum::ASSET->value) {
            return;
        }

        if (
            $category->asset_account_id &&
            $category->accum_dep_account_id &&
            $category->dep_expense_account_id
        ) {
            return;
        }

        $accounts = $this->accountService->createCategoryAccounts(
            categoryName: $category->name,
            categoryCode: $category->code,
            categoryType: $category->category_type
        );

        $category->update([
            'asset_account_id' => $accounts['asset_account_id'],
            'accum_dep_account_id' => $accounts['accum_dep_account_id'],
            'dep_expense_account_id' => $accounts['dep_expense_account_id'],
        ]);
    }

    public function updateStatus(Category $category, array $status)
    {
        return DB::transaction(function () use ($category, $status) {
            return $this->categoryRepo->updateStatus($category, $status);
        });
    }
}
