<?php

namespace App\Http\Controllers\Admin;

use App\DataTables\AcademyDataTable;
use App\DataTables\AcademyMemberDataTable;
use App\DataTables\AcademyAssignedMemberDataTable;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Academy\StoreAcademyRequest;
use App\Http\Requests\Admin\Academy\UpdateAcademyRequest;
use App\Models\Academy;
use App\Models\Coach;
use App\Models\Dependent;
use App\Models\Game;
use App\Models\Member;
use App\Repositories\AcademyRepository;
use App\Services\AcademyMemberDependentsBundleImportService;
use App\Services\DependentImportExportService;
use App\Services\MemberImportExportService;
use App\DataTables\SlotDataTable;
use App\Enums\MemberStatusEnum;
use App\Enums\MemberTypeEnum;
use App\Enums\RelationEnum;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use App\Services\AdminImportSpreadsheetParser;
use Symfony\Component\HttpFoundation\StreamedResponse;

class AcademyController extends Controller
{
    public function __construct(
        protected AcademyRepository $repository,
        protected MemberImportExportService $memberImportExportService,
        protected DependentImportExportService $dependentImportExportService,
        protected AcademyMemberDependentsBundleImportService $academyMemberDependentsBundleImportService
    ) {}

    public function index(AcademyDataTable $dataTable)
    {
        return view('admin.academies.index', ['dataTable' => $dataTable->html()]);
    }

    public function datatable(AcademyDataTable $dataTable)
    {
        return $dataTable->ajax();
    }

    public function create()
    {
        $coaches = Coach::active()->orderBy('name')->get(['id', 'game_id', 'name']);
        $games = Game::active()->orderBy('name')->get();
        return view('admin.academies.create', compact('coaches', 'games'));
    }

    public function store(StoreAcademyRequest $request)
    {
        $this->repository->create($request->validated());
        return response()->json([
            'message' => __('dashboard.academy_created'),
            'redirect_url' => route('admin.academies.index'),
        ]);
    }

    public function show(Academy $academy, SlotDataTable $dataTable, AcademyAssignedMemberDataTable $membersDataTable)
    {
        $academy->load(['game', 'coaches', 'schedules.court']);

        $dataTable->withFilters(['academy_id' => $academy->id]);
        $slotsDataTableHtml = $dataTable->html();

        $membersDataTable->academyId = $academy->id;
        $membersDataTableHtml = $membersDataTable->html();

        return view('admin.academies.show', compact('academy'))
            ->with('dataTable', $slotsDataTableHtml)
            ->with('membersDataTable', $membersDataTableHtml);
    }

    public function edit(Academy $academy)
    {
        $coaches = Coach::active()->orderBy('name')->get(['id', 'game_id', 'name']);
        $games = Game::active()->orderBy('name')->get();
        return view('admin.academies.edit', compact('academy', 'coaches', 'games'));
    }

    public function update(UpdateAcademyRequest $request, Academy $academy)
    {
        $this->repository->update($academy, $request->validated());
        return response()->json([
            'message' => __('dashboard.academy_updated'),
            'redirect_url' => route('admin.academies.index'),
        ]);
    }

    public function destroy(Academy $academy)
    {
        $result = $this->repository->delete($academy);
        return response()->json([
            'message' => $result['success'] ? __('dashboard.academy_deleted') : __('dashboard.cannot_delete_has_relations'),
            'success' => $result['success'],
            'relations' => $result['relations'],
        ]);
    }

    public function updateStatus(Academy $academy)
    {
        $result = $this->repository->statusToggle($academy, '', 'is_active');
        return response()->json([
            'message' => $result['message'],
            'success' => $result['success'],
        ]);
    }

    public function bulkDelete(Request $request)
    {
        $request->validate(['ids' => 'required|array']);
        $result = $this->repository->bulkDelete($request->ids);
        return response()->json([
            'message' => __('dashboard.deleted_success'),
            'success' => true,
            'data' => $result,
        ]);
    }

    public function bulkStatus(Request $request)
    {
        $request->validate(['ids' => 'required|array', 'status' => 'required|boolean']);
        $result = $this->repository->bulkStatus($request->ids, $request->status, 'is_active');
        return response()->json([
            'message' => __('dashboard.status_updated'),
            'success' => true,
            'data' => $result,
        ]);
    }

    public function manageMembers(Academy $academy, AcademyMemberDataTable $dataTable)
    {
        $dataTable->academyId = $academy->id;
        $recordTypes = [
            ['value' => 'member', 'label' => 'Member'],
            ['value' => 'dependent', 'label' => 'Dependent'],
        ];

        return view('admin.academies.members', compact('academy'))
            ->with('recordTypes', $recordTypes)
            ->with('dataTable', $dataTable->html())
            ->with('memberStatusEnumLabels', MemberStatusEnum::labels())
            ->with('memberStatusEnumColors', MemberStatusEnum::colors());
    }

    public function membersDatatable(Academy $academy, AcademyMemberDataTable $dataTable)
    {
        $dataTable->academyId = $academy->id;
        return $dataTable->ajax();
    }

    public function syncMembers(Request $request, Academy $academy)
    {
        $request->validate([
            'member_ids'    => 'present|array',
            'member_ids.*'  => 'integer|exists:members,id',
            'dependent_ids'   => 'nullable|array',
            'dependent_ids.*' => 'integer|exists:dependents,id',
        ]);

        $this->repository->syncMembers(
            $academy,
            (array) $request->input('member_ids', []),
            (array) $request->input('dependent_ids', [])
        );

        return response()->json(['message' => __('dashboard.saved_successfully'), 'success' => true]);
    }

    public function memberIds(Academy $academy)
    {
        $memberIds    = $academy->members()->pluck('members.id')->map('intval')->all();
        $dependentIds = $academy->enrolledDependents()->pluck('dependents.id')->map('intval')->all();

        return response()->json([
            'member_ids'    => $memberIds,
            'dependent_ids' => $dependentIds,
        ]);
    }

    public function selectedMembers(Academy $academy)
    {
        // Enrolled members (from academy_member, assignment_type = 1)
        $memberRows = $academy->members()
            ->select(['members.id', 'members.name', 'members.phone', 'members.status'])
            ->orderBy('members.name')
            ->get()
            ->map(static fn ($m) => [
                'row_key'      => 'member-' . $m->id,
                'member_id'    => $m->id,
                'dependent_id' => null,
                'id'           => $m->id,
                'name'         => $m->name,
                'phone'        => $m->phone,
                'status'       => (int) $m->status,
                'type'         => __('dashboard.member'),
                'type_value'   => 1,
            ]);

        // Enrolled dependents (from academy_dependent)
        $dependentRows = $academy->enrolledDependents()
            ->select(['dependents.id', 'dependents.name', 'dependents.phone', 'dependents.assigned_member_id', 'dependents.status'])
            ->orderBy('dependents.name')
            ->get()
            ->map(static fn ($d) => [
                'row_key'      => 'dependent-' . $d->id,
                'member_id'    => $d->assigned_member_id,
                'dependent_id' => $d->id,
                'id'           => $d->id,
                'name'         => $d->name,
                'phone'        => $d->phone,
                'status'       => (int) $d->status,
                'type'         => __('dashboard.dependent'),
                'type_value'   => 2,
            ]);

        $all = $memberRows->concat($dependentRows)->sortBy('name')->values()->all();

        return response()->json(['members' => $all]);
    }

    public function assignedMembersDatatable(Academy $academy, AcademyAssignedMemberDataTable $dataTable)
    {
        $dataTable->academyId = $academy->id;
        return $dataTable->ajax();
    }

    public function importMembers(Request $request, Academy $academy)
    {
        $request->validate([
            'file' => ['required', 'file', 'mimes:csv,txt,xlsx,xls'],
        ]);

        $file = $request->file('file');
        $parsed = $this->parseImportFile($file);

        if (empty($parsed['rows'])) {
            return redirect()
                ->route('admin.academies.members.import.page', $academy->id)
                ->with('import_report', [
                    'added' => 0,
                    'skipped' => 0,
                    'errors' => [__('The uploaded file has no data rows.')],
                ]);
        }

        $report = $this->isLegacyAcademyImportHeader($parsed['header'])
            ? $this->runLegacyAcademyImport($academy, $parsed['rows'])
            : $this->academyMemberDependentsBundleImportService->importBundles($academy, $parsed['rows']);

        return redirect()
            ->route('admin.academies.members.import.page', $academy->id)
            ->with('import_report', $report);
    }

    public function exportAvailableMembers(Academy $academy): StreamedResponse
    {
        return $this->exportAvailableByType($academy, 'member');
    }

    public function exportAvailableDependents(Academy $academy): StreamedResponse
    {
        return $this->exportAvailableByType($academy, 'dependent');
    }

    public function exportAvailableAll(Academy $academy): StreamedResponse
    {
        return $this->exportAvailableByType($academy, null);
    }

    public function exportSelectedAssignedMembers(Academy $academy): StreamedResponse
    {
        return $this->exportSelectedAssignedByType($academy, 'member');
    }

    public function exportSelectedAssignedDependents(Academy $academy): StreamedResponse
    {
        return $this->exportSelectedAssignedByType($academy, 'dependent');
    }

    public function exportSelectedAssignedAll(Academy $academy): StreamedResponse
    {
        return $this->exportSelectedAssignedByType($academy, null);
    }

    public function importMembersPage(Academy $academy)
    {
        return view('admin.academies.import-members', compact('academy'));
    }

    public function downloadAcademyImportTemplate(Academy $academy): StreamedResponse
    {
        return $this->academyMemberDependentsBundleImportService->streamBundleTemplate(
            sprintf('academy_%d_member_dependents_import_template.xlsx', $academy->id)
        );
    }

    public function downloadAcademyImportLegacyTemplate(Academy $academy): StreamedResponse
    {
        $headers = [
            'Content-Type' => 'text/csv; charset=UTF-8',
            'Content-Disposition' => 'attachment; filename="academy_' . $academy->id . '_import_simple_template.csv"',
        ];

        return response()->stream(function (): void {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['name', 'phone', 'type']);
            fputcsv($out, ['John Member', '01000000000', 'member']);
            fputcsv($out, ['Ali Dependent', '0500000000', 'dependent']);
            fclose($out);
        }, 200, $headers);
    }

    public function exportSelectedMembers(Request $request, Academy $academy): StreamedResponse
    {
        $memberIds = $this->normalizeIds($request->input('member_ids', []));
        $dependentIds = $this->normalizeIds($request->input('dependent_ids', []));

        $members = empty($memberIds)
            ? collect()
            : Member::query()
                ->whereIn('id', $memberIds)
                ->get(['id', 'name', 'phone']);

        $dependents = empty($dependentIds)
            ? collect()
            : Dependent::query()
                ->whereIn('id', $dependentIds)
                ->get(['id', 'assigned_member_id', 'name', 'phone']);

        $headers = [
            'Content-Type' => 'text/csv; charset=UTF-8',
            'Content-Disposition' => 'attachment; filename="academy_' . $academy->id . '_selected_members_' . now()->format('Ymd_His') . '.csv"',
        ];

        return response()->stream(function () use ($members, $dependents): void {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['type', 'name', 'phone', 'member_id', 'dependent_id']);

            foreach ($members as $m) {
                fputcsv($out, ['member', $m->name, $m->phone, $m->id, '']);
            }

            foreach ($dependents as $d) {
                fputcsv($out, ['dependent', $d->name, $d->phone, $d->assigned_member_id, $d->id]);
            }

            fclose($out);
        }, 200, $headers);
    }

    private function attachImportedMembersToAcademy(Academy $academy, UploadedFile $file): void
    {
        $rows = $this->parseRowsWithHeader($file);
        if (empty($rows)) {
            return;
        }

        $emails = [];
        $phones = [];
        foreach ($rows as $row) {
            $email = strtolower(trim((string) ($row['email'] ?? '')));
            $phone = $this->normalizePhoneInput($row['phone'] ?? null);
            if ($email !== '') {
                $emails[] = $email;
            }
            if (!empty($phone)) {
                $phones[] = $phone;
            }
        }

        $emails = array_values(array_unique($emails));
        $phones = array_values(array_unique($phones));
        if (empty($emails) && empty($phones)) {
            return;
        }

        $ids = Member::query()
            ->where(function ($q) use ($emails, $phones) {
                if (!empty($emails)) {
                    $q->whereIn('email', $emails);
                }
                if (!empty($phones)) {
                    $q->orWhereIn('phone', $phones);
                }
            })
            ->pluck('id')
            ->map(fn ($v) => (int) $v)
            ->values()
            ->all();

        if (empty($ids)) {
            return;
        }

        $syncData = [];
        foreach ($ids as $id) {
            $syncData[$id] = ['assignment_type' => 1];
        }
        $academy->members()->syncWithoutDetaching($syncData);
    }

    private function attachImportedDependentsToAcademy(Academy $academy, UploadedFile $file): void
    {
        $rows = $this->parseRowsWithHeader($file);
        if (empty($rows)) {
            return;
        }

        $memberPhones = [];
        foreach ($rows as $row) {
            $mp = $this->normalizePhoneInput($row['member_phone'] ?? null);
            if (!empty($mp)) {
                $memberPhones[] = $mp;
            }
        }
        $memberPhones = array_values(array_unique($memberPhones));
        if (empty($memberPhones)) {
            return;
        }

        $membersByPhone = Member::query()
            ->whereIn('phone', $memberPhones)
            ->pluck('id', 'phone')
            ->map(fn ($v) => (int) $v)
            ->toArray();

        $parentIds = array_values(array_unique(array_values($membersByPhone)));
        if (empty($parentIds)) {
            return;
        }

        $dependents = Dependent::query()
            ->whereIn('assigned_member_id', $parentIds)
            ->get(['id', 'assigned_member_id', 'name', 'phone', 'dob']);

        $index = [];
        foreach ($dependents as $d) {
            $key = $this->dependentMatchKey(
                (int) $d->assigned_member_id,
                (string) $d->name,
                $d->phone,
                $d->dob?->format('Y-m-d')
            );
            $index[$key] = (int) $d->id;
        }

        $dependentIds = [];
        foreach ($rows as $row) {
            $memberPhone = $this->normalizePhoneInput($row['member_phone'] ?? null);
            $parentId = $memberPhone ? ($membersByPhone[$memberPhone] ?? null) : null;
            if (!$parentId) {
                continue;
            }

            $key = $this->dependentMatchKey(
                (int) $parentId,
                (string) ($row['name'] ?? ''),
                $this->normalizePhoneInput($row['phone'] ?? null),
                !empty($row['dob']) ? trim((string) $row['dob']) : null
            );

            if (isset($index[$key])) {
                $dependentIds[] = (int) $index[$key];
            }
        }

        $dependentIds = array_values(array_unique(array_filter(array_map('intval', $dependentIds))));
        if (!empty($dependentIds)) {
            $academy->enrolledDependents()->syncWithoutDetaching($dependentIds);
        }
    }

    /**
     * @return array{header: list<string>, rows: list<array<string, string|null>>}  // values normalized to strings; dates as Y-m-d when recognized
     */
    private function parseImportFile(UploadedFile $file): array
    {
        return AdminImportSpreadsheetParser::parse($file);
    }

    private function parseRowsWithHeader(UploadedFile $file): array
    {
        return $this->parseImportFile($file)['rows'];
    }

    /**
     * @param  list<string>  $header
     */
    private function isLegacyAcademyImportHeader(array $header): bool
    {
        $set = array_flip($header);
        $hasType = isset($set['type']);
        $hasBundleHint = isset($set['member_phone']) || isset($set['member_name']) || isset($set['dep1_name']);

        return $hasType && ! $hasBundleHint;
    }

    /**
     * @param  list<array<string, string|null>>  $rows
     * @return array<string, mixed>
     */
    private function runLegacyAcademyImport(Academy $academy, array $rows): array
    {
        $added = 0;
        $skipped = 0;
        $errors = [];
        $memberIds = [];
        $dependentIds = [];

        foreach ($rows as $index => $row) {
            $line = $index + 2;
            $name = trim((string) ($row['name'] ?? ''));
            $phone = $this->normalizePhoneInput($row['phone'] ?? null);
            $type = strtolower(trim((string) ($row['type'] ?? '')));

            if ($this->isBlankLegacyImportRow($row)) {
                continue;
            }

            if ($name === '' && $type === '') {
                continue;
            }

            if ($name === '' || $type === '') {
                $skipped++;
                $errors[] = "Row {$line}: name and type are required.";
                continue;
            }

            if (in_array($type, ['member', 'm'], true)) {
                $member = Member::query()
                    ->where('phone', $phone)
                    ->first();

                if (! $member) {
                    if (empty($phone)) {
                        $skipped++;
                        $errors[] = "Row {$line}: member phone is required for new member creation.";
                        continue;
                    }
                    $member = Member::query()->create([
                        'name' => $name,
                        'phone' => $phone,
                        'member_type' => MemberTypeEnum::ALL_INCLUSIVE->value,
                        'status' => MemberStatusEnum::ACTIVE->value,
                        'password' => bcrypt('12345678'),
                    ]);
                    $added++;
                }
                $memberIds[] = (int) $member->id;
                continue;
            }

            if (in_array($type, ['dependent', 'd'], true)) {
                $dependent = Dependent::query()
                    ->when(! empty($phone), fn ($q) => $q->where('phone', $phone))
                    ->when(empty($phone), fn ($q) => $q->where('name', $name))
                    ->first();

                if (! $dependent) {
                    $skipped++;
                    $errors[] = "Row {$line}: dependent not found (name/phone). Cannot create new dependent without parent member.";
                    continue;
                }
                $dependentIds[] = (int) $dependent->id;
                continue;
            }

            $skipped++;
            $errors[] = "Row {$line}: type must be member or dependent.";
        }

        $memberIds = array_values(array_unique($memberIds));
        $dependentIds = array_values(array_unique($dependentIds));
        if (! empty($memberIds)) {
            $syncData = [];
            foreach ($memberIds as $id) {
                $syncData[$id] = ['assignment_type' => 1];
            }
            $academy->members()->syncWithoutDetaching($syncData);
        }
        if (! empty($dependentIds)) {
            $academy->enrolledDependents()->syncWithoutDetaching($dependentIds);
        }

        return ['added' => $added, 'skipped' => $skipped, 'errors' => $errors];
    }

    /**
     * @param  array<string, string|null|int|float>  $row
     */
    private function isBlankLegacyImportRow(array $row): bool
    {
        foreach ($row as $value) {
            if ($this->legacyImportCellHasContent($value)) {
                return false;
            }
        }

        return true;
    }

    private function legacyImportCellHasContent(mixed $value): bool
    {
        if ($value === null) {
            return false;
        }
        if (is_bool($value)) {
            return $value;
        }
        if (is_numeric($value)) {
            return ((float) $value) != 0.0;
        }
        $s = trim((string) $value);
        if ($s === '') {
            return false;
        }
        if (preg_match('/^="(.*)"$/', $s, $m)) {
            $s = trim((string) $m[1]);
        }

        return $s !== '';
    }

    private function normalizePhoneInput(?string $value): ?string
    {
        if ($value === null) {
            return null;
        }

        $v = trim($value);
        if ($v === '') {
            return null;
        }

        if (preg_match('/^="(.*)"$/', $v, $m)) {
            return trim((string) $m[1]);
        }

        return $v;
    }

    private function dependentMatchKey(int $parentId, string $name, ?string $phone, ?string $dob): string
    {
        return strtolower(trim((string) $parentId))
            . '|' . strtolower(trim($name))
            . '|' . strtolower(trim((string) ($phone ?? '')))
            . '|' . trim((string) ($dob ?? ''));
    }

    private function exportAvailableByType(Academy $academy, ?string $type): StreamedResponse
    {
        $headers = [
            'Content-Type' => 'text/csv; charset=UTF-8',
            'Content-Disposition' => 'attachment; filename="academy_' . $academy->id . '_available_' . ($type ?? 'all') . '_' . now()->format('Ymd_His') . '.csv"',
        ];

        $academyId = (int) $academy->id;

        $members = Member::query()
            ->leftJoin('academy_member as am', function ($join) use ($academyId) {
                $join->on('am.member_id', '=', 'members.id')
                    ->where('am.academy_id', '=', $academyId);
            })
            ->whereNull('am.id')
            ->select(['members.name', 'members.phone'])
            ->orderBy('members.name')
            ->get()
            ->map(fn ($m) => ['name' => $m->name, 'phone' => $m->phone, 'type' => 'member']);

        $dependents = Dependent::query()
            ->leftJoin('academy_dependent as ad', function ($join) use ($academyId) {
                $join->on('ad.dependent_id', '=', 'dependents.id')
                    ->where('ad.academy_id', '=', $academyId);
            })
            ->whereNull('ad.id')
            ->select(['dependents.name', 'dependents.phone'])
            ->orderBy('dependents.name')
            ->get()
            ->map(fn ($d) => ['name' => $d->name, 'phone' => $d->phone, 'type' => 'dependent']);

        $rows = match ($type) {
            'member' => $members,
            'dependent' => $dependents,
            default => $members->concat($dependents)->values(),
        };

        return response()->stream(function () use ($rows): void {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['name', 'phone', 'type']);
            foreach ($rows as $r) {
                fputcsv($out, [$r['name'], $r['phone'], $r['type']]);
            }
            fclose($out);
        }, 200, $headers);
    }

    private function exportSelectedAssignedByType(Academy $academy, ?string $type): StreamedResponse
    {
        $headers = [
            'Content-Type' => 'text/csv; charset=UTF-8',
            'Content-Disposition' => 'attachment; filename="academy_' . $academy->id . '_selected_' . ($type ?? 'all') . '_' . now()->format('Ymd_His') . '.csv"',
        ];

        $members = $academy->members()
            ->select(['members.name', 'members.phone'])
            ->orderBy('members.name')
            ->get()
            ->map(fn ($m) => ['name' => $m->name, 'phone' => $m->phone, 'type' => 'member']);

        $dependents = $academy->enrolledDependents()
            ->select(['dependents.name', 'dependents.phone'])
            ->orderBy('dependents.name')
            ->get()
            ->map(fn ($d) => ['name' => $d->name, 'phone' => $d->phone, 'type' => 'dependent']);

        $rows = match ($type) {
            'member' => $members,
            'dependent' => $dependents,
            default => $members->concat($dependents)->values(),
        };

        return response()->stream(function () use ($rows): void {
            $out = fopen('php://output', 'w');
            fputcsv($out, ['name', 'phone', 'type']);
            foreach ($rows as $r) {
                fputcsv($out, [$r['name'], $r['phone'], $r['type']]);
            }
            fclose($out);
        }, 200, $headers);
    }

    private function normalizeIds(array|string|null $raw): array
    {
        if ($raw === null) {
            return [];
        }

        $ids = is_array($raw) ? $raw : explode(',', (string) $raw);
        return array_values(array_unique(array_filter(
            array_map(static fn ($v) => (int) $v, $ids),
            static fn (int $v) => $v > 0
        )));
    }
}
