<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\FinancialAccount;
use App\Models\FixedAsset;
use App\Models\FixedAssetMaintenance;
use App\Models\User;
use App\Rules\SameBranchAs;
use App\Services\AuditService;
use App\Services\BranchAccessService;
use App\Services\FixedAssetService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class FixedAssetController extends Controller
{
    public function __construct(private BranchAccessService $branchAccess)
    {
    }

    public function index(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);
        $query = FixedAsset::query()->with(['branch','custodian'])->whereIn('branch_id', $branchIds);

        if ($request->filled('branch_id')) {
            $branchId = (int) $request->input('branch_id');
            abort_unless($branchIds->contains($branchId), 403);
            $query->where('branch_id', $branchId);
        }
        if ($request->filled('status')) $query->where('status', $request->input('status'));
        if ($request->filled('q')) {
            $term = trim((string) $request->input('q'));
            $query->where(function ($q) use ($term): void {
                $q->where('asset_number', 'like', '%'.$term.'%')
                    ->orWhere('name', 'like', '%'.$term.'%')
                    ->orWhere('category', 'like', '%'.$term.'%')
                    ->orWhere('serial_number', 'like', '%'.$term.'%');
            });
        }

        $summaryQuery = clone $query;
        $summary = [
            'cost' => $summaryQuery->sum('acquisition_cost'),
            'accumulated' => (clone $query)->sum('accumulated_depreciation'),
            'book_value' => (clone $query)->sum('book_value'),
            'count' => (clone $query)->count(),
        ];
        $assets = $query->orderBy('asset_number')->paginate(30)->withQueryString();
        $branches = Branch::whereIn('id', $branchIds)->orderBy('name')->get();

        return view('fixed-assets.index', compact('assets','branches','summary'));
    }

    public function create(Request $request): View
    {
        $branchIds = $this->allowedBranchIds($request);
        return view('fixed-assets.create', [
            'branches' => Branch::whereIn('id', $branchIds)->where('status', 'active')->orderBy('name')->get(),
            'accounts' => FinancialAccount::with('branch')->whereIn('branch_id', $branchIds)->where('status', 'active')->orderBy('branch_id')->orderBy('name')->get(),
            'custodians' => $this->assignedStaff($branchIds),
        ]);
    }

    public function store(Request $request, FixedAssetService $service): RedirectResponse
    {
        $data = $request->validate($this->createRules());
        $this->assertBranchAccess($request, (int) $data['branch_id']);
        $request->validate([
            'financial_account_id' => [
                'nullable',
                new SameBranchAs('financial_accounts', (int) $data['branch_id'], 'The purchase account must belong to the asset branch.'),
            ],
        ]);
        $this->assertCustodianAccess($data['custodian_user_id'] ?? null, (int) $data['branch_id']);

        $asset = $service->create($data, $request->user());
        AuditService::record('fixed_asset.created', $asset, [], [
            'asset_number' => $asset->asset_number,
            'acquisition_cost' => $asset->acquisition_cost,
            'acquisition_source' => $asset->acquisition_source,
            'depreciation_method' => $asset->depreciation_method,
        ], $asset->branch_id);

        return redirect()->route('fixed-assets.show', $asset)->with('success', 'Fixed asset registered and accounting entries posted.');
    }

    public function show(Request $request, FixedAsset $fixedAsset, FixedAssetService $service): View
    {
        $this->assertBranchAccess($request, $fixedAsset->branch_id);
        $fixedAsset->load(['branch','financialAccount','custodian','depreciations.poster','maintenances.financialAccount','maintenances.recorder']);
        $accounts = FinancialAccount::where('branch_id', $fixedAsset->branch_id)->where('status', 'active')->orderBy('name')->get();
        $custodians = $this->assignedStaff(collect([$fixedAsset->branch_id]));
        $monthlyDepreciation = $service->monthlyDepreciation($fixedAsset);

        return view('fixed-assets.show', compact('fixedAsset','accounts','custodians','monthlyDepreciation'));
    }

    public function update(Request $request, FixedAsset $fixedAsset): RedirectResponse
    {
        $this->assertBranchAccess($request, $fixedAsset->branch_id);
        $data = $request->validate([
            'name' => ['required','string','max:160'],
            'category' => ['required','string','max:120'],
            'description' => ['nullable','string','max:5000'],
            'serial_number' => ['nullable','string','max:160'],
            'location' => ['nullable','string','max:255'],
            'custodian_user_id' => ['nullable','integer','exists:users,id'],
            'status' => ['required', Rule::in(array_keys(FixedAsset::STATUSES))],
            'next_maintenance_date' => ['nullable','date'],
            'notes' => ['nullable','string','max:5000'],
        ]);
        $this->assertCustodianAccess($data['custodian_user_id'] ?? null, $fixedAsset->branch_id);
        $before = $fixedAsset->only(array_keys($data));
        $fixedAsset->update($data + ['updated_by' => $request->user()->id]);
        AuditService::record('fixed_asset.updated', $fixedAsset, $before, $fixedAsset->only(array_keys($data)), $fixedAsset->branch_id);

        return back()->with('success', 'Asset details updated.');
    }

    public function depreciate(Request $request, FixedAsset $fixedAsset, FixedAssetService $service): RedirectResponse
    {
        $this->assertBranchAccess($request, $fixedAsset->branch_id);
        $data = $request->validate([
            'period' => ['required','date_format:Y-m'],
            'notes' => ['nullable','string','max:2000'],
        ]);
        $record = $service->postDepreciation($fixedAsset, $data['period'], $data['notes'] ?? null, $request->user());
        AuditService::record('fixed_asset.depreciation_posted', $record, [], [
            'asset_id' => $fixedAsset->id,'period' => $record->period_date->format('Y-m'),'amount' => $record->amount,
            'book_value_after' => $record->book_value_after,
        ], $fixedAsset->branch_id);

        return back()->with('success', 'Depreciation posted for '.$record->period_date->format('M Y').'.');
    }

    public function maintenance(Request $request, FixedAsset $fixedAsset, FixedAssetService $service): RedirectResponse
    {
        $this->assertBranchAccess($request, $fixedAsset->branch_id);
        $data = $request->validate([
            'maintenance_date' => ['required','date'],
            'maintenance_type' => ['required', Rule::in(array_keys(FixedAssetMaintenance::TYPES))],
            'description' => ['required','string','max:5000'],
            'supplier' => ['nullable','string','max:255'],
            'cost' => ['required','regex:/^\d+(?:\.\d{1,2})?$/'],
            'financial_account_id' => ['nullable','integer','exists:financial_accounts,id'],
            'reference' => ['nullable','string','max:160'],
            'next_due_date' => ['nullable','date','after_or_equal:maintenance_date'],
            'notes' => ['nullable','string','max:5000'],
        ]);
        $request->validate([
            'financial_account_id' => [
                'nullable',
                new SameBranchAs('financial_accounts', (int) $fixedAsset->branch_id, 'The maintenance account must belong to the asset branch.'),
            ],
        ]);
        $record = $service->recordMaintenance($fixedAsset, $data, $request->user());
        AuditService::record('fixed_asset.maintenance_recorded', $record, [], [
            'asset_id' => $fixedAsset->id,'maintenance_number' => $record->maintenance_number,'cost' => $record->cost,
            'next_due_date' => $record->next_due_date?->toDateString(),
        ], $fixedAsset->branch_id);

        return back()->with('success', 'Maintenance record saved.');
    }

    private function createRules(): array
    {
        return [
            'branch_id' => ['required','integer','exists:branches,id'],
            'name' => ['required','string','max:160'],
            'category' => ['required','string','max:120'],
            'description' => ['nullable','string','max:5000'],
            'serial_number' => ['nullable','string','max:160'],
            'supplier' => ['nullable','string','max:255'],
            'purchase_reference' => ['nullable','string','max:160'],
            'acquisition_source' => ['required', Rule::in(array_keys(FixedAsset::ACQUISITION_SOURCES))],
            'acquisition_date' => ['required','date'],
            'depreciation_start_date' => ['nullable','date','after_or_equal:acquisition_date'],
            'acquisition_cost' => ['required','regex:/^\d+(?:\.\d{1,2})?$/'],
            'residual_value' => ['required','regex:/^\d+(?:\.\d{1,2})?$/'],
            'depreciation_method' => ['required', Rule::in(array_keys(FixedAsset::DEPRECIATION_METHODS))],
            'useful_life_months' => ['nullable','integer','between:1,1200','required_if:depreciation_method,straight_line'],
            'financial_account_id' => ['nullable','integer','exists:financial_accounts,id','required_if:acquisition_source,financial_account'],
            'location' => ['nullable','string','max:255'],
            'custodian_user_id' => ['nullable','integer','exists:users,id'],
            'status' => ['required', Rule::in(array_keys(FixedAsset::STATUSES))],
            'next_maintenance_date' => ['nullable','date'],
            'notes' => ['nullable','string','max:5000'],
        ];
    }

    private function assertCustodianAccess(mixed $userId, int $branchId): void
    {
        if (! $userId) return;
        $custodian = User::findOrFail((int) $userId);
        if ($custodian->status !== 'active' || ! $this->branchAccess->isAssignedToBranch($custodian, $branchId)) {
            throw ValidationException::withMessages(['custodian_user_id' => 'The selected custodian must be active staff assigned to the asset branch.']);
        }
    }

    private function assignedStaff($branchIds)
    {
        $ids = collect($branchIds)->map(fn ($id) => (int) $id)->unique()->values()->all();

        return User::query()
            ->where('status', 'active')
            ->where(function ($query) use ($ids): void {
                $query->whereIn('primary_branch_id', $ids)
                    ->orWhereHas('branches', fn ($branches) => $branches->whereIn('branches.id', $ids));
            })
            ->orderBy('name')
            ->get();
    }

    private function allowedBranchIds(Request $request)
    {
        return collect($this->branchAccess->accessibleBranchIds($request->user()));
    }

    private function assertBranchAccess(Request $request, int $branchId): void
    {
        $this->branchAccess->assertBranchAccessible($request->user(), $branchId);
    }
}
