<?php

declare(strict_types=1);

namespace App\Http\Controllers\Accounting;

use App\Http\Controllers\AbstractController;
use App\Models\Accounting\Account;
use App\Models\Accounting\AccountTransaction;
use App\Models\Market\Store;
use App\Http\Controllers\Helpers\Permissions\UserPermissions;
use App\Enums\UserActions;
use App\Enums\UserTypes;
use App\Http\Controllers\Helpers\ResponseHelper;
use App\Support\ModuleHelper;
use Hypervel\Http\Request;
use Hypervel\Support\Facades\Auth;
use Hypervel\Support\Facades\DB;
use Hyperf\Stringable\Str;

class AccountingController extends AbstractController
{
    // ─── helpers ────────────────────────────────────────────────────────────

    private function resolveType(): UserTypes
    {
        $user = Auth::user();
        return $user->acct_type instanceof UserTypes
            ? $user->acct_type
            : (UserTypes::tryFrom($user->acct_type) ?? UserTypes::PUBLIC);
    }

    private function isBig3(UserTypes $t): bool
    {
        return in_array($t, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR], true);
    }

    private function isStoreLevel(UserTypes $t): bool
    {
        return in_array($t, [UserTypes::STORE_OWNER, UserTypes::STORE_MANAGER], true);
    }

    /** IDs of stores the current user owns or manages (including descendants). */
    private function userStoreIds(): array
    {
        $user = Auth::user();
        $allowedUserIds = array_merge([$user->id], $user->getAllDescendants()->pluck('id')->toArray());

        return Store::where(function ($q) use ($allowedUserIds) {
            $q->whereIn('owner_id', $allowedUserIds)
              ->orWhereIn('manager_id', $allowedUserIds)
              ->orWhereHas('managers', function ($mq) use ($allowedUserIds) {
                  $mq->whereIn('user_id', $allowedUserIds);
              });
        })->pluck('id')->toArray();
    }

    /** Apply store_id scope to a query based on the caller's role. */
    private function scopeAccounts($query, array $storeIds, bool $isBig3)
    {
        if ($isBig3) {
            return $query->whereNull('store_id');
        }
        return $query->whereIn('store_id', $storeIds);
    }

    /** Check store-level access. Returns false if the accounting_store module is disabled. */
    private function guardStore(): bool
    {
        return ModuleHelper::isEnabled('accounting_store');
    }

    // ─── public endpoints ────────────────────────────────────────────────────

    /**
     * Full Chart of Accounts tree.
     */
    public function getAccountsTree(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type    = $this->resolveType();
        $isBig3  = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $storeIds = $isBig3 ? [] : $this->userStoreIds();

        $base = Account::whereNull('parent_id')->where('is_active', true);
        $base = $this->scopeAccounts($base, $storeIds, $isBig3);

        $accounts = $base
            ->with(['children' => function ($q) use ($storeIds, $isBig3) {
                $q = $q->where('is_active', true);
                $q = $this->scopeAccounts($q, $storeIds, $isBig3);
                $q->with(['children' => function ($q2) use ($storeIds, $isBig3) {
                    $q2 = $q2->where('is_active', true);
                    $q2 = $this->scopeAccounts($q2, $storeIds, $isBig3);
                }]);
            }])
            ->orderBy('id')
            ->get();

        return response()->json(['success' => true, 'data' => $accounts]);
    }

    /**
     * Leaf accounts for the Daily Entry form.
     */
    public function getLeafAccounts(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $storeIds = $isBig3 ? [] : $this->userStoreIds();

        $base = Account::where('is_active', true)->whereDoesntHave('children');
        $base = $this->scopeAccounts($base, $storeIds, $isBig3);

        $leafAccounts = $base
            ->with('parent.parent')
            ->orderBy('id')
            ->get();

        return response()->json(['success' => true, 'data' => $leafAccounts]);
    }

    /**
     * Daily transactions for a given date.
     */
    public function getDailyTransactions(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $storeIds = $isBig3 ? [] : $this->userStoreIds();
        $date     = $request->input('date', date('Y-m-d'));

        $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id');

        $transactions = AccountTransaction::with(['account'])
            ->whereIn('account_id', $accountIds)
            ->whereDate('transaction_date', $date)
            ->get();

        $mapped = [];
        foreach ($transactions as $txn) {
            $mapped[$txn->account_id] = [
                'id'     => $txn->id,
                'amount' => $txn->amount,
                'notes'  => $txn->notes,
            ];
        }

        return response()->json(['success' => true, 'date' => $date, 'data' => $mapped]);
    }

    /**
     * Bulk save daily transactions.
     */
    public function saveDailyTransactions(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $storeIds = $isBig3 ? [] : $this->userStoreIds();
        $date     = $request->input('date');
        $entries  = $request->input('entries', []);
        $userId   = Auth::id();

        // Allowed account IDs for this user (prevent writing to other stores' accounts)
        $allowedAccountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->flip()->all();

        DB::beginTransaction();
        try {
            foreach ($entries as $accountId => $entryData) {
                if (!array_key_exists($accountId, $allowedAccountIds)) {
                    continue; // skip accounts not owned by this user
                }

                $amount  = (float)($entryData['amount'] ?? 0);
                $account = Account::find($accountId);
                if (!$account) {
                    continue;
                }

                $flow = $account->default_flow
                    ?: ((strtoupper((string) $account->type) === 'REVENUE' || strtoupper((string) $account->type) === 'LIABILITY') ? 'INCOME' : 'EXPENSE');

                $txn = AccountTransaction::where('account_id', $accountId)
                    ->whereDate('transaction_date', $date)
                    ->first();

                if ($amount == 0) {
                    if ($txn) {
                        $txn->delete();
                    }
                    continue;
                }

                if ($txn) {
                    $txn->amount     = $amount;
                    $txn->notes      = $entryData['notes'] ?? null;
                    $txn->updated_by = $userId;
                    $txn->save();
                } else {
                    AccountTransaction::create([
                        'hashkey'          => Str::random(32),
                        'account_id'       => $accountId,
                        'amount'           => $amount,
                        'flow'             => $flow,
                        'transaction_date' => $date,
                        'notes'            => $entryData['notes'] ?? null,
                        'created_by'       => $userId,
                        'updated_by'       => $userId,
                    ]);
                }
            }
            DB::commit();
            return response()->json(['success' => true, 'message' => 'Daily transactions saved.']);
        } catch (\Exception $e) {
            DB::rollBack();
            return response()->json(['success' => false, 'message' => 'Error saving: ' . $e->getMessage()]);
        }
    }

    /**
     * Transaction list for CRUD tab.
     */
    public function listTransactions(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $storeIds   = $isBig3 ? [] : $this->userStoreIds();
        $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id');

        $query = AccountTransaction::with(['account', 'creator', 'updater'])
            ->whereIn('account_id', $accountIds);

        if ($request->filled('account_id')) {
            $query->where('account_id', $request->account_id);
        }
        if ($request->filled('date_from')) {
            $query->whereDate('transaction_date', '>=', $request->date_from);
        }
        if ($request->filled('date_to')) {
            $query->whereDate('transaction_date', '<=', $request->date_to);
        }

        $transactions = $query->orderBy('transaction_date', 'desc')->paginate(50);

        return response()->json(['success' => true, 'data' => $transactions]);
    }

    /**
     * Delete single transaction.
     */
    public function deleteTransaction(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);
        $storeIds = $isBig3 ? [] : $this->userStoreIds();
        $allowedAccountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->all();

        $txn = AccountTransaction::find($request->id);
        if ($txn && in_array($txn->account_id, $allowedAccountIds)) {
            $txn->delete();
        }

        return response()->json(['success' => true]);
    }

    /**
     * Monthly matrix report (Rows = Days, Columns = Accounts).
     */
    public function getMonthlyReport(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $storeIds   = $isBig3 ? [] : $this->userStoreIds();
        $year       = $request->input('year', date('Y'));
        $month      = $request->input('month', date('m'));

        $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id')->all();

        $transactions = AccountTransaction::whereYear('transaction_date', $year)
            ->whereMonth('transaction_date', $month)
            ->whereIn('account_id', $accountIds)
            ->get();

        $daysInMonth = cal_days_in_month(CAL_GREGORIAN, (int)$month, (int)$year);
        $matrix      = [];
        for ($i = 1; $i <= $daysInMonth; $i++) {
            $matrix[$i] = [];
        }

        foreach ($transactions as $txn) {
            $day = (int)date('d', strtotime($txn->transaction_date));
            $matrix[$day][$txn->account_id] = ($matrix[$day][$txn->account_id] ?? 0) + $txn->amount;
        }

        $columnTotals = [];
        foreach ($transactions as $txn) {
            $columnTotals[$txn->account_id] = ($columnTotals[$txn->account_id] ?? 0) + $txn->amount;
        }

        return response()->json([
            'success'      => true,
            'year'         => $year,
            'month'        => $month,
            'days_in_month' => $daysInMonth,
            'matrix'       => $matrix,
            'column_totals' => $columnTotals,
        ]);
    }

    /**
     * Create a new account node. For store-level users the account is
     * automatically scoped to their store (first owned store).
     */
    public function createAccount(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $name        = trim((string) $request->input('name', ''));
        $accountType = strtoupper(trim((string) $request->input('type', '')));
        $defaultFlow = strtoupper(trim((string) $request->input('default_flow', '')));
        $parentId    = $request->input('parent_id') ?: null;
        $description = $request->input('description') ?: null;

        if ($name === '' || $accountType === '') {
            return response()->json(['success' => false, 'message' => 'Name and type are required.'], 422);
        }
        if (!in_array($defaultFlow, ['INCOME', 'EXPENSE'], true)) {
            $defaultFlow = in_array($accountType, ['REVENUE', 'LIABILITY']) ? 'INCOME' : 'EXPENSE';
        }
        if ($parentId !== null && !Account::where('id', $parentId)->exists()) {
            return response()->json(['success' => false, 'message' => 'Parent account not found.'], 422);
        }

        // Determine store_id: null for Big3 (global), first store for store-level users
        $storeId = null;
        if (!$isBig3) {
            $storeIds = $this->userStoreIds();
            if (empty($storeIds)) {
                return response()->json(['success' => false, 'message' => 'No store found for your account. Create a store first.'], 422);
            }
            // If a parent is supplied, inherit its store_id; otherwise use the request-supplied store_id or the user's first store
            if ($parentId) {
                $parent  = Account::find($parentId);
                $storeId = $parent?->store_id ?? $storeIds[0];
            } else {
                $requestedStoreId = $request->input('store_id');
                $storeId = ($requestedStoreId && in_array($requestedStoreId, $storeIds))
                    ? (int)$requestedStoreId
                    : $storeIds[0];
            }
        }

        try {
            $account = Account::create([
                'hashkey'      => Str::random(40),
                'parent_id'    => $parentId,
                'store_id'     => $storeId,
                'type'         => $accountType,
                'default_flow' => $defaultFlow,
                'name'         => $name,
                'description'  => $description,
                'is_active'    => true,
                'created_by'   => Auth::id(),
                'updated_by'   => Auth::id(),
            ]);
        } catch (\Throwable $e) {
            \Hypervel\Support\Facades\Log::error('createAccount failed', [
                'message'   => $e->getMessage(),
                'name'      => $name,
                'type'      => $accountType,
                'parent_id' => $parentId,
                'store_id'  => $storeId,
                'user_id'   => Auth::id(),
            ]);
            return response()->json([
                'success' => false,
                'message' => 'Could not create account: ' . $e->getMessage(),
            ], 422);
        }

        return response()->json(['success' => true, 'data' => $account]);
    }

    /**
     * Update editable fields (name, type, default_flow, description).
     */
    public function updateAccount(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $account = Account::find($request->input('id'));
        if (!$account) {
            return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
        }

        // Store-level users may only edit their own store's accounts
        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);
        if (!$isBig3) {
            if (!$this->guardStore()) {
                return ResponseHelper::returnUnauthorized();
            }
            $storeIds = $this->userStoreIds();
            if (!in_array($account->store_id, $storeIds)) {
                return ResponseHelper::returnUnauthorized();
            }
        }

        if ($request->filled('name')) {
            $account->name = trim((string) $request->input('name'));
        }
        if ($request->filled('type')) {
            $account->type = strtoupper(trim((string) $request->input('type')));
        }
        if ($request->filled('default_flow')) {
            $flow = strtoupper(trim((string) $request->input('default_flow')));
            if (!in_array($flow, ['INCOME', 'EXPENSE'], true)) {
                return response()->json(['success' => false, 'message' => 'default_flow must be INCOME or EXPENSE.'], 422);
            }
            $account->default_flow = $flow;
        }
        if ($request->has('description')) {
            $account->description = $request->input('description') ?: null;
        }

        $account->updated_by = Auth::id();
        $account->save();

        return response()->json(['success' => true, 'data' => $account]);
    }

    /**
     * Archive (soft-disable) an account.
     */
    public function archiveAccount(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $account = Account::find($request->input('id'));
        if (!$account) {
            return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);
        if (!$isBig3) {
            if (!$this->guardStore()) {
                return ResponseHelper::returnUnauthorized();
            }
            $storeIds = $this->userStoreIds();
            if (!in_array($account->store_id, $storeIds)) {
                return ResponseHelper::returnUnauthorized();
            }
        }

        if (Account::where('parent_id', $account->id)->where('is_active', true)->exists()) {
            return response()->json(['success' => false, 'message' => 'Archive child accounts first.'], 422);
        }
        if (AccountTransaction::where('account_id', $account->id)->exists()) {
            return response()->json(['success' => false, 'message' => 'Cannot archive: account has transactions. Move or delete them first.'], 422);
        }

        $account->is_active  = false;
        $account->updated_by = Auth::id();
        $account->save();

        return response()->json(['success' => true]);
    }

    /**
     * Move an account under a new parent (or to root). Refuses cycles.
     */
    public function moveAccount(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $account = Account::find($request->input('id'));
        if (!$account) {
            return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);
        if (!$isBig3) {
            if (!$this->guardStore()) {
                return ResponseHelper::returnUnauthorized();
            }
            $storeIds = $this->userStoreIds();
            if (!in_array($account->store_id, $storeIds)) {
                return ResponseHelper::returnUnauthorized();
            }
        }

        $newParentId = $request->input('parent_id');
        $newParentId = ($newParentId === null || $newParentId === '' || $newParentId == 0) ? null : (int)$newParentId;

        if ($newParentId === (int)$account->id) {
            return response()->json(['success' => false, 'message' => 'An account cannot be its own parent.'], 422);
        }

        if ($newParentId !== null) {
            $parent = Account::find($newParentId);
            if (!$parent) {
                return response()->json(['success' => false, 'message' => 'Target parent not found.'], 422);
            }

            $cursor = $parent;
            $hops   = 0;
            while ($cursor && $hops < 1000) {
                if ((int)$cursor->id === (int)$account->id) {
                    return response()->json(['success' => false, 'message' => 'Cannot move an account under one of its own descendants.'], 422);
                }
                $cursor = $cursor->parent_id ? Account::find($cursor->parent_id) : null;
                $hops++;
            }
        }

        $account->parent_id  = $newParentId;
        $account->updated_by = Auth::id();
        $account->save();

        return response()->json(['success' => true]);
    }

    /**
     * Re-activate an archived account.
     */
    public function restoreAccount(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $account = Account::find($request->input('id'));
        if (!$account) {
            return response()->json(['success' => false, 'message' => 'Account not found.'], 404);
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);
        if (!$isBig3) {
            if (!$this->guardStore()) {
                return ResponseHelper::returnUnauthorized();
            }
            $storeIds = $this->userStoreIds();
            if (!in_array($account->store_id, $storeIds)) {
                return ResponseHelper::returnUnauthorized();
            }
        }

        $account->is_active  = true;
        $account->updated_by = Auth::id();
        $account->save();

        return response()->json(['success' => true]);
    }

    /**
     * Theme metadata + drift summary for Manage Accounts.
     */
    public function getThemeInfo(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $current    = \App\Support\AccountingTheme::current();
        $definition = \App\Support\AccountingTheme::definition($current);

        return response()->json([
            'success'    => true,
            'current'    => $current,
            'definition' => [
                'label'       => $definition['label'] ?? $current,
                'description' => $definition['description'] ?? null,
                'version'     => $definition['version'] ?? 1,
            ],
            'options' => \App\Support\AccountingTheme::options(),
            'counts'  => [
                'theme_tagged' => Account::where('theme_key', $current)->count(),
                'user_added'   => Account::whereNull('theme_key')->count(),
                'archived'     => Account::where('is_active', false)->count(),
            ],
        ]);
    }

    /**
     * Read-only drift report.
     */
    public function getThemeDrift(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        return response()->json([
            'success' => true,
            'data'    => \App\Support\AccountingTheme::drift(),
        ]);
    }

    /**
     * Switch the active accounting theme.
     */
    public function setTheme(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        $key = trim((string) $request->input('key', ''));
        if ($key === '' || !\array_key_exists($key, \App\Support\AccountingTheme::all())) {
            return response()->json(['success' => false, 'message' => 'Unknown theme.'], 422);
        }

        \App\Models\SystemSetting::setValue('accounting_theme', $key, 'accounting', 'string');

        return response()->json(['success' => true, 'key' => $key]);
    }

    /**
     * Idempotently re-apply the active theme (additive only).
     */
    public function applyTheme(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ManageAccounting)) {
            return ResponseHelper::returnUnauthorized();
        }

        try {
            $stats = \App\Support\AccountingTheme::apply();
        } catch (\Throwable $e) {
            return response()->json([
                'success' => false,
                'message' => 'Failed to apply theme: ' . $e->getMessage(),
            ], 422);
        }

        return response()->json([
            'success' => true,
            'message' => sprintf(
                'Theme "%s" applied: %d created, %d updated, %d unchanged.',
                $stats['theme_key'],
                $stats['created'],
                $stats['stamped'],
                $stats['skipped']
            ),
            'data' => $stats,
        ]);
    }

    /**
     * Recent transactions for the Reports page.
     */
    public function listReports(Request $request)
    {
        if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::ViewAccountingReports)) {
            return ResponseHelper::returnUnauthorized();
        }

        $type   = $this->resolveType();
        $isBig3 = $this->isBig3($type);

        if (!$isBig3 && !$this->guardStore()) {
            return ResponseHelper::returnUnauthorized();
        }

        $storeIds   = $isBig3 ? [] : $this->userStoreIds();
        $accountIds = $this->scopeAccounts(Account::query(), $storeIds, $isBig3)->pluck('id');

        $transactions = AccountTransaction::with(['account', 'creator'])
            ->whereIn('account_id', $accountIds)
            ->orderBy('transaction_date', 'desc')
            ->limit(100)
            ->get();

        return response()->json(['success' => true, 'transactions' => $transactions]);
    }
}
