<?php

namespace Modules\Accounting\Services;

use App\Models\ActivityLogModel;
use App\Models\AssetCategoryModel;
use App\Models\AssetDisposalModel;
use App\Models\FixedAssetModel;

class AssetDisposalService
{
    protected AssetDisposalModel $disposals;
    protected FixedAssetModel $assets;
    protected AssetCategoryModel $categories;
    protected JournalEntryService $journals;
    protected ActivityLogModel $activity;

    public function __construct()
    {
        $this->disposals  = new AssetDisposalModel();
        $this->assets     = new FixedAssetModel();
        $this->categories = new AssetCategoryModel();
        $this->journals   = new JournalEntryService();
        $this->activity   = new ActivityLogModel();
    }

    public function create(array $input, ?int $actingUserId): array
    {
        $assetId = (int) ($input['fixed_asset_id'] ?? 0);
        $asset = $this->assets->find($assetId);
        if (! $asset) {
            return ['success' => false, 'errors' => ['fixed_asset_id' => 'Fixed asset not found.']];
        }
        if ($asset['status'] === 'disposed') {
            return ['success' => false, 'errors' => ['fixed_asset_id' => 'Asset is already disposed.']];
        }

        $proceeds  = round((float) ($input['proceeds'] ?? 0), 2);
        $cost      = (float) $asset['cost'];
        $accum     = (float) $asset['accum_depreciation'];
        $bookValue = (float) $asset['book_value'];
        $gainLoss  = round($proceeds - $bookValue, 2);

        $data = [
            'number'               => $this->disposals->nextNumber(),
            'fixed_asset_id'       => $assetId,
            'disposal_date'        => $input['disposal_date'] ?? date('Y-m-d'),
            'proceeds'             => $proceeds,
            'cost'                 => $cost,
            'accum_depreciation'   => $accum,
            'gain_loss'            => $gainLoss,
            'proceeds_account_id'  => ! empty($input['proceeds_account_id']) ? (int) $input['proceeds_account_id'] : null,
            'gain_loss_account_id' => ! empty($input['gain_loss_account_id']) ? (int) $input['gain_loss_account_id'] : null,
            'status'               => 'draft',
            'notes'                => ! empty($input['notes']) ? trim((string) $input['notes']) : null,
            'created_by'           => $actingUserId,
            'updated_by'           => $actingUserId,
        ];

        if (! $this->disposals->insert($data, true)) {
            return ['success' => false, 'errors' => $this->disposals->errors()];
        }

        $id = (int) $this->disposals->getInsertID();
        $this->activity->log('asset_disposals', 'created', $actingUserId, "Created asset disposal #{$id} ({$data['number']})", (string) $id);

        return ['success' => true, 'id' => $id];
    }

    public function update(int $id, array $input, ?int $actingUserId): array
    {
        $disposal = $this->disposals->find($id);
        if (! $disposal) {
            return ['success' => false, 'errors' => ['id' => 'Asset disposal not found.']];
        }
        if ($disposal['status'] !== 'draft') {
            return ['success' => false, 'errors' => ['status' => 'Only draft disposals can be edited.']];
        }

        $assetId = (int) ($input['fixed_asset_id'] ?? $disposal['fixed_asset_id']);
        $asset = $this->assets->find($assetId);
        if (! $asset) {
            return ['success' => false, 'errors' => ['fixed_asset_id' => 'Fixed asset not found.']];
        }
        if ($asset['status'] === 'disposed' && $assetId !== (int) $disposal['fixed_asset_id']) {
            return ['success' => false, 'errors' => ['fixed_asset_id' => 'Asset is already disposed.']];
        }

        $proceeds  = round((float) ($input['proceeds'] ?? 0), 2);
        $cost      = (float) $asset['cost'];
        $accum     = (float) $asset['accum_depreciation'];
        $bookValue = (float) $asset['book_value'];
        $gainLoss  = round($proceeds - $bookValue, 2);

        $this->disposals->update($id, [
            'fixed_asset_id'       => $assetId,
            'disposal_date'        => $input['disposal_date'] ?? $disposal['disposal_date'],
            'proceeds'             => $proceeds,
            'cost'                 => $cost,
            'accum_depreciation'   => $accum,
            'gain_loss'            => $gainLoss,
            'proceeds_account_id'  => ! empty($input['proceeds_account_id']) ? (int) $input['proceeds_account_id'] : null,
            'gain_loss_account_id' => ! empty($input['gain_loss_account_id']) ? (int) $input['gain_loss_account_id'] : null,
            'notes'                => ! empty($input['notes']) ? trim((string) $input['notes']) : null,
            'updated_by'           => $actingUserId,
        ]);

        $this->activity->log('asset_disposals', 'updated', $actingUserId, "Updated asset disposal #{$id}", (string) $id);

        return ['success' => true];
    }

    public function post(int $id, ?int $actingUserId): array
    {
        $disposal = $this->disposals->find($id);
        if (! $disposal) {
            return ['success' => false, 'error' => 'Asset disposal not found.'];
        }
        if ($disposal['status'] !== 'draft') {
            return ['success' => false, 'error' => 'Only draft disposals can be posted.'];
        }

        $asset = $this->assets->find((int) $disposal['fixed_asset_id']);
        if (! $asset) {
            return ['success' => false, 'error' => 'Fixed asset not found.'];
        }
        if ($asset['status'] === 'disposed') {
            return ['success' => false, 'error' => 'Asset is already disposed.'];
        }

        $category = $this->categories->find((int) $asset['category_id']);
        $assetAccountId = (int) ($asset['asset_account_id'] ?: ($category['asset_account_id'] ?? 0));
        $accumAccountId = (int) ($asset['accum_depr_account_id'] ?: ($category['accum_depr_account_id'] ?? 0));

        if ($assetAccountId <= 0 || $accumAccountId <= 0) {
            return ['success' => false, 'error' => 'Asset is missing cost or accumulated depreciation accounts.'];
        }

        $cost     = round((float) $disposal['cost'], 2);
        $accum    = round((float) $disposal['accum_depreciation'], 2);
        $proceeds = round((float) $disposal['proceeds'], 2);
        $book     = round($cost - $accum, 2);
        $gainLoss = round($proceeds - $book, 2);

        $proceedsAccountId = (int) ($disposal['proceeds_account_id'] ?? 0);
        $gainLossAccountId = (int) ($disposal['gain_loss_account_id'] ?? 0);

        if ($proceeds > 0 && $proceedsAccountId <= 0) {
            return ['success' => false, 'error' => 'Proceeds account is required when proceeds are greater than zero.'];
        }
        if (abs($gainLoss) > 0.00001 && $gainLossAccountId <= 0) {
            return ['success' => false, 'error' => 'Gain/loss account is required when disposal produces a gain or loss.'];
        }

        // Standard disposal:
        // Dr Accumulated Depreciation (accum)
        // Dr Cash/Proceeds (proceeds) if > 0
        // Dr Loss (if book > proceeds) OR Cr Gain (if proceeds > book)
        // Cr Asset Cost (cost)
        $jeLines = [];

        if ($accum > 0) {
            $jeLines[] = [
                'account_id'  => $accumAccountId,
                'description' => 'Remove accumulated depreciation ' . $disposal['number'],
                'debit'       => $accum,
                'credit'      => 0,
            ];
        }

        if ($proceeds > 0) {
            $jeLines[] = [
                'account_id'  => $proceedsAccountId,
                'description' => 'Disposal proceeds ' . $disposal['number'],
                'debit'       => $proceeds,
                'credit'      => 0,
            ];
        }

        if ($gainLoss < 0) {
            $loss = abs($gainLoss);
            $jeLines[] = [
                'account_id'  => $gainLossAccountId,
                'description' => 'Loss on disposal ' . $disposal['number'],
                'debit'       => $loss,
                'credit'      => 0,
            ];
        }

        $jeLines[] = [
            'account_id'  => $assetAccountId,
            'description' => 'Remove asset cost ' . $disposal['number'],
            'debit'       => 0,
            'credit'      => $cost,
        ];

        if ($gainLoss > 0) {
            $jeLines[] = [
                'account_id'  => $gainLossAccountId,
                'description' => 'Gain on disposal ' . $disposal['number'],
                'debit'       => 0,
                'credit'      => $gainLoss,
            ];
        }

        // Edge case: fully depreciated, no proceeds, no gain/loss — need balancing line if accum == cost
        // If cost == accum and proceeds == 0: Dr accum, Cr asset — balanced with 2 lines.
        // If accum == 0 and proceeds == 0 and cost > 0: Dr loss (full cost), Cr asset.
        if (count($jeLines) < 2) {
            return ['success' => false, 'error' => 'Unable to build a balanced disposal journal entry.'];
        }

        $posted = $this->journals->createAndPost([
            'entry_date'  => $disposal['disposal_date'],
            'reference'   => $disposal['number'],
            'description' => 'Asset disposal ' . $disposal['number'] . ' — ' . $asset['name'],
            'lines'       => $jeLines,
        ], $actingUserId);

        if (! $posted['success']) {
            return [
                'success' => false,
                'error'   => $posted['error'] ?? ($posted['errors']['lines'] ?? 'Failed to post disposal journal entry.'),
            ];
        }

        $this->assets->update((int) $asset['id'], [
            'status'     => 'disposed',
            'book_value' => 0,
            'updated_by' => $actingUserId,
        ]);

        $this->disposals->update($id, [
            'status'           => 'posted',
            'gain_loss'        => $gainLoss,
            'journal_entry_id' => (int) $posted['id'],
            'posted_at'        => date('Y-m-d H:i:s'),
            'updated_by'       => $actingUserId,
        ]);
        $this->activity->log('asset_disposals', 'posted', $actingUserId, "Posted asset disposal #{$id}", (string) $id);

        return ['success' => true];
    }

    public function delete(int $id, ?int $actingUserId): array
    {
        $disposal = $this->disposals->find($id);
        if (! $disposal) {
            return ['success' => false, 'error' => 'Asset disposal not found.'];
        }
        if ($disposal['status'] !== 'draft') {
            return ['success' => false, 'error' => 'Only draft disposals can be deleted.'];
        }

        $this->disposals->delete($id);
        $this->activity->log('asset_disposals', 'deleted', $actingUserId, "Deleted asset disposal #{$id}", (string) $id);

        return ['success' => true];
    }
}
