<?php

namespace App\Http\Controllers;

use App\Models\Branch;
use App\Models\FixedAsset;
use App\Models\FixedAssetDepreciation;
use App\Models\FixedAssetMaintenance;
use App\Services\MoneyMath;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;

class FixedAssetReportController extends Controller
{
    public const REPORTS = [
        'register' => 'Asset Register',
        'depreciation' => 'Depreciation Report',
        'maintenance' => 'Maintenance Report',
    ];

    public function index(Request $request, MoneyMath $money): View
    {
        $scope = $this->scope($request);
        $report = (string) $request->input('report', 'register');
        abort_unless(isset(self::REPORTS[$report]), 404);
        $data = $this->data($report, $scope, $money);
        $reports = self::REPORTS;
        return view('fixed-assets.reports', array_merge($scope, compact('report','data','reports')));
    }

    public function export(Request $request, MoneyMath $money): StreamedResponse
    {
        $scope = $this->scope($request);
        $report = (string) $request->input('report', 'register');
        abort_unless(isset(self::REPORTS[$report]), 404);
        $data = $this->data($report, $scope, $money);
        [$columns, $rows] = $this->csvRows($report, $data);
        $filename = 'fixed-assets-'.$report.'-'.$scope['from'].'-to-'.$scope['to'].'.csv';

        return response()->streamDownload(function () use ($report, $scope, $columns, $rows): void {
            $out = fopen('php://output', 'w');
            fwrite($out, "\xEF\xBB\xBF");
            fputcsv($out, [self::REPORTS[$report]]);
            fputcsv($out, ['From', $scope['from'], 'To', $scope['to']]);
            fputcsv($out, ['Branches', $scope['selectedBranches']->pluck('name')->join(', ')]);
            fputcsv($out, []);
            fputcsv($out, $columns);
            foreach ($rows as $row) fputcsv($out, $row);
            fclose($out);
        }, $filename, ['Content-Type' => 'text/csv; charset=UTF-8']);
    }

    private function data(string $report, array $scope, MoneyMath $money): array
    {
        $assets = FixedAsset::with(['branch','custodian'])
            ->whereIn('branch_id', $scope['selectedBranchIds'])
            ->orderBy('asset_number')->get();
        $depreciations = FixedAssetDepreciation::with(['asset.branch','poster'])
            ->whereHas('asset', fn ($q) => $q->whereIn('branch_id', $scope['selectedBranchIds']))
            ->whereDate('period_date', '>=', $scope['from'])
            ->whereDate('period_date', '<=', $scope['to'])
            ->orderBy('period_date')->orderBy('id')->get();
        $maintenance = FixedAssetMaintenance::with(['asset','branch','financialAccount','recorder'])
            ->whereIn('branch_id', $scope['selectedBranchIds'])
            ->whereDate('maintenance_date', '>=', $scope['from'])
            ->whereDate('maintenance_date', '<=', $scope['to'])
            ->orderBy('maintenance_date')->orderBy('id')->get();

        $sum = function ($rows, string $field) use ($money): string {
            $minor = collect($rows)->sum(fn ($row) => $money->toMinor((string) $row->{$field}));
            return $money->fromMinor($minor);
        };

        return [
            'rows' => match ($report) {
                'register' => $assets,
                'depreciation' => $depreciations,
                'maintenance' => $maintenance,
            },
            'asset_count' => $assets->count(),
            'total_cost' => $sum($assets, 'acquisition_cost'),
            'accumulated_depreciation' => $sum($assets, 'accumulated_depreciation'),
            'net_book_value' => $sum($assets, 'book_value'),
            'period_depreciation' => $sum($depreciations, 'amount'),
            'period_maintenance' => $sum($maintenance, 'cost'),
            'by_category' => $assets->groupBy('category')->map(fn ($items, $category) => [
                'category' => $category,
                'count' => $items->count(),
                'book_value' => $sum($items, 'book_value'),
            ])->sortBy('category')->values(),
        ];
    }

    private function scope(Request $request): array
    {
        $allowed = collect(app(\App\Services\BranchAccessService::class)->accessibleBranchIds($request->user()))
            ->map(fn ($id) => (int) $id)
            ->values();
        abort_if($allowed->isEmpty(), 403, 'No branch access is assigned to this user.');

        $validated = validator([
            'from' => $request->input('from', now()->startOfYear()->toDateString()),
            'to' => $request->input('to', now()->toDateString()),
            'branch_ids' => $request->input('branch_ids', []),
        ], [
            'from' => ['required','date'],
            'to' => ['required','date','after_or_equal:from'],
            'branch_ids' => ['array'],
            'branch_ids.*' => ['integer','exists:branches,id'],
        ])->validate();

        $requested = collect($validated['branch_ids'] ?? [])->map(fn ($id) => (int) $id)->unique()->values();
        abort_if($requested->diff($allowed)->isNotEmpty(), 403, 'One or more selected branches are outside your assigned scope.');
        $selectedBranchIds = $requested->isEmpty() ? $allowed : $requested;
        $branches = Branch::whereIn('id', $allowed)->orderBy('name')->get();
        $selectedBranches = $branches->whereIn('id', $selectedBranchIds)->values();
        $from = CarbonImmutable::parse($validated['from'])->toDateString();
        $to = CarbonImmutable::parse($validated['to'])->toDateString();
        return compact('branches','selectedBranchIds','selectedBranches','from','to');
    }

    private function csvRows(string $report, array $data): array
    {
        return match ($report) {
            'register' => [[
                'Asset Number','Name','Category','Branch','Acquired','Cost','Accumulated Depreciation','Book Value','Status','Location','Custodian',
            ], collect($data['rows'])->map(fn ($asset) => [
                $asset->asset_number,$asset->name,$asset->category,$asset->branch->name,$asset->acquisition_date->format('Y-m-d'),
                $asset->acquisition_cost,$asset->accumulated_depreciation,$asset->book_value,$asset->status,$asset->location,$asset->custodian?->name,
            ])->all()],
            'depreciation' => [[
                'Period','Asset','Name','Branch','Amount','Accumulated After','Book Value After','Journal','Posted By',
            ], collect($data['rows'])->map(fn ($row) => [
                $row->period_date->format('Y-m-d'),$row->asset->asset_number,$row->asset->name,$row->asset->branch->name,
                $row->amount,$row->accumulated_after,$row->book_value_after,$row->journal_number,$row->poster?->name,
            ])->all()],
            'maintenance' => [[
                'Date','Maintenance Number','Asset','Branch','Type','Description','Supplier','Cost','Account','Reference','Next Due',
            ], collect($data['rows'])->map(fn ($row) => [
                $row->maintenance_date->format('Y-m-d'),$row->maintenance_number,$row->asset->asset_number,$row->branch->name,
                $row->maintenance_type,$row->description,$row->supplier,$row->cost,$row->financialAccount?->name,$row->reference,$row->next_due_date?->format('Y-m-d'),
            ])->all()],
        };
    }
}
