<?php

namespace App\Http\Controllers\Admin;

use App\Exports\SimpleArrayExport;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\UserBinaryCarry;
use App\Models\BinaryLedger;
use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Excel as WriterType;
use Maatwebsite\Excel\Facades\Excel;

class TreeController extends Controller
{
    public function index(Request $request): View
    {
        $search = trim((string) $request->query('q', ''));
        $mode = in_array($request->query('mode'), ['placement', 'sponsor'], true)
            ? (string) $request->query('mode')
            : 'placement';
        $status = in_array($request->query('status'), ['all', 'active', 'pending', 'blocked'], true)
            ? (string) $request->query('status')
            : 'all';
        $leg = in_array($request->query('leg'), ['all', 'left', 'right'], true)
            ? (string) $request->query('leg')
            : 'all';
        $depth = max(1, min((int) $request->query('depth', 3), 10));
        $nodeSearch = trim((string) $request->query('node_q', ''));

        $member = null;
        $directs = collect();
        $nodes = collect();
        $levelSummary = collect();
        $treeStats = [
            'total_nodes' => 0,
            'left_nodes' => 0,
            'right_nodes' => 0,
            'left_active' => 0,
            'right_active' => 0,
        ];
        $legTotals = ['left' => 0, 'right' => 0];
        $carry = null;

        if ($search !== '') {
            $member = User::query()
                ->where('member_code', $search)
                ->orWhere('mobile', $search)
                ->orWhere('email', $search)
                ->when(is_numeric($search), fn ($query) => $query->orWhere('id', (int) $search))
                ->first();

            if ($member) {
                $nodesQuery = $this->networkQuery($member->id, $mode);
                $this->applyFilters($nodesQuery, $mode, $status, $leg, $depth, $nodeSearch);

                $statsRows = (clone $nodesQuery)
                    ->selectRaw("path_leg, COUNT(*) as total_nodes, SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_nodes")
                    ->groupBy('path_leg')
                    ->get();

                $treeStats = [
                    'total_nodes' => (int) (clone $nodesQuery)->count(),
                    'left_nodes' => (int) ($statsRows->firstWhere('path_leg', 'left')->total_nodes ?? 0),
                    'right_nodes' => (int) ($statsRows->firstWhere('path_leg', 'right')->total_nodes ?? 0),
                    'left_active' => (int) ($statsRows->firstWhere('path_leg', 'left')->active_nodes ?? 0),
                    'right_active' => (int) ($statsRows->firstWhere('path_leg', 'right')->active_nodes ?? 0),
                ];

                $levelSummary = (clone $nodesQuery)
                    ->selectRaw("depth, COUNT(*) as total_nodes, SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_nodes")
                    ->groupBy('depth')
                    ->orderBy('depth')
                    ->get();

                $nodes = $nodesQuery
                    ->orderBy('depth')
                    ->orderBy('member_code')
                    ->paginate(25)
                    ->withQueryString();

                $directs = $this->networkQuery($member->id, $mode)
                    ->where('depth', 1)
                    ->orderBy('path_leg')
                    ->get();

                $legTotals['left'] = (float) BinaryLedger::query()
                    ->where('beneficiary_user_id', $member->id)
                    ->where('leg', 'left')
                    ->selectRaw("COALESCE(SUM(CASE WHEN direction='credit' THEN units ELSE -units END),0) as total")
                    ->value('total');
                $legTotals['right'] = (float) BinaryLedger::query()
                    ->where('beneficiary_user_id', $member->id)
                    ->where('leg', 'right')
                    ->selectRaw("COALESCE(SUM(CASE WHEN direction='credit' THEN units ELSE -units END),0) as total")
                    ->value('total');
                $carry = UserBinaryCarry::query()->where('user_id', $member->id)->first();
            }
        }

        return view('admin.tree', [
            'search' => $search,
            'mode' => $mode,
            'member' => $member,
            'directs' => $directs,
            'nodes' => $nodes,
            'status' => $status,
            'leg' => $leg,
            'depth' => $depth,
            'nodeSearch' => $nodeSearch,
            'treeStats' => $treeStats,
            'levelSummary' => $levelSummary,
            'legTotals' => $legTotals,
            'carry' => $carry,
            'anomalies' => $this->anomalyStats(),
        ]);
    }

    public function export(Request $request)
    {
        $search = trim((string) $request->query('q', ''));
        $mode = in_array($request->query('mode'), ['placement', 'sponsor'], true)
            ? (string) $request->query('mode')
            : 'placement';
        $status = in_array($request->query('status'), ['all', 'active', 'pending', 'blocked'], true)
            ? (string) $request->query('status')
            : 'all';
        $leg = in_array($request->query('leg'), ['all', 'left', 'right'], true)
            ? (string) $request->query('leg')
            : 'all';
        $depth = max(1, min((int) $request->query('depth', 3), 10));
        $nodeSearch = trim((string) $request->query('node_q', ''));
        $format = strtolower((string) $request->query('format', 'pdf'));

        if ($search === '') {
            return redirect()->route('admin.tree')->withErrors(['q' => 'Search a member to export.']);
        }

        $member = User::query()
            ->where('member_code', $search)
            ->orWhere('mobile', $search)
            ->orWhere('email', $search)
            ->when(is_numeric($search), fn ($query) => $query->orWhere('id', (int) $search))
            ->first();

        if (! $member) {
            return redirect()->route('admin.tree')->withErrors(['q' => 'Member not found.']);
        }

        $exportQuery = $this->networkQuery($member->id, $mode);
        $this->applyFilters($exportQuery, $mode, $status, $leg, $depth, $nodeSearch);
        $rows = $exportQuery
            ->orderBy('depth')
            ->orderBy('member_code')
            ->limit(1000)
            ->get();

        $legTotals = [
            'left' => (float) BinaryLedger::query()
                ->where('beneficiary_user_id', $member->id)
                ->where('leg', 'left')
                ->selectRaw("COALESCE(SUM(CASE WHEN direction='credit' THEN units ELSE -units END),0) as total")
                ->value('total'),
            'right' => (float) BinaryLedger::query()
                ->where('beneficiary_user_id', $member->id)
                ->where('leg', 'right')
                ->selectRaw("COALESCE(SUM(CASE WHEN direction='credit' THEN units ELSE -units END),0) as total")
                ->value('total'),
        ];

        $headings = ['Depth', 'Leg', 'Member Code', 'Name', 'Mobile', 'Email', 'Status'];
        $exportRows = $rows->map(fn ($row) => [
            $row->depth,
            $row->path_leg,
            $row->member_code,
            $row->name,
            $row->mobile,
            $row->email,
            $row->status,
        ])->all();

        if (in_array($format, ['xlsx', 'csv'], true)) {
            $writerType = $format === 'csv' ? WriterType::CSV : WriterType::XLSX;

            return Excel::download(
                new SimpleArrayExport($exportRows, $headings),
                'tree-'.$member->member_code.'.'.$format,
                $writerType
            );
        }

        $pdf = Pdf::loadView('admin.tree-pdf', [
            'member' => $member,
            'rows' => $rows,
            'legTotals' => $legTotals,
            'mode' => $mode,
            'status' => $status,
            'leg' => $leg,
            'depth' => $depth,
        ]);

        return $pdf->download('tree-'.$member->member_code.'.pdf');
    }

    private function networkQuery(int $memberId, string $mode)
    {
        if ($mode === 'sponsor') {
            return DB::table('users as u')
                ->where('u.role', 'user')
                ->where('u.sponsor_id', $memberId)
                ->selectRaw("COALESCE(u.placement_leg, 'left') as path_leg")
                ->selectRaw('1 as depth')
                ->addSelect([
                    'u.id',
                    'u.member_code',
                    'u.name',
                    'u.mobile',
                    'u.email',
                    'u.status',
                    'u.placement_leg',
                ]);
        }

        return DB::table('member_tree_closure as mtc')
            ->join('users as u', 'u.id', '=', 'mtc.descendant_id')
            ->where('u.role', 'user')
            ->where('mtc.ancestor_id', $memberId)
            ->where('mtc.depth', '>', 0)
            ->addSelect([
                'mtc.path_leg',
                'mtc.depth',
                'u.id',
                'u.member_code',
                'u.name',
                'u.mobile',
                'u.email',
                'u.status',
                'u.placement_leg',
            ]);
    }

    private function applyFilters(
        $query,
        string $mode,
        string $status,
        string $leg,
        int $depth,
        string $nodeSearch
    ): void {
        if ($mode === 'placement') {
            $query->where('depth', '<=', $depth);
        }

        if ($status !== 'all') {
            $query->where('status', $status);
        }

        if ($leg !== 'all') {
            $query->where('path_leg', $leg);
        }

        if ($nodeSearch !== '') {
            $query->where(function ($builder) use ($nodeSearch): void {
                $builder->where('member_code', 'like', "%{$nodeSearch}%")
                    ->orWhere('name', 'like', "%{$nodeSearch}%")
                    ->orWhere('mobile', 'like', "%{$nodeSearch}%")
                    ->orWhere('email', 'like', "%{$nodeSearch}%");
            });
        }
    }

    /**
     * @return array<string, int>
     */
    private function anomalyStats(): array
    {
        $orphanNodes = User::query()
            ->where('role', 'user')
            ->whereNotNull('sponsor_id')
            ->whereNull('placement_parent_id')
            ->count();

        $selfPlacementLoops = User::query()
            ->where('role', 'user')
            ->whereColumn('placement_parent_id', 'id')
            ->count();

        $duplicateLegGroups = User::query()
            ->where('role', 'user')
            ->whereNotNull('placement_parent_id')
            ->whereNotNull('placement_leg')
            ->selectRaw('placement_parent_id, placement_leg, COUNT(*) as total')
            ->groupBy('placement_parent_id', 'placement_leg')
            ->havingRaw('COUNT(*) > 1')
            ->get()
            ->count();

        $missingClosureEntries = User::query()
            ->where('role', 'user')
            ->leftJoin('member_tree_closure as mtc', function ($join): void {
                $join->on('mtc.ancestor_id', '=', 'users.id')
                    ->on('mtc.descendant_id', '=', 'users.id')
                    ->where('mtc.depth', '=', 0);
            })
            ->whereNull('mtc.id')
            ->count();

        return [
            'orphan_nodes' => $orphanNodes,
            'self_loops' => $selfPlacementLoops,
            'duplicate_leg_groups' => $duplicateLegGroups,
            'missing_closure_entries' => $missingClosureEntries,
        ];
    }
}
