<?php

namespace App\Http\Controllers;

use App\Models\Ballot;
use App\Models\Candidate;
use App\Models\Election;
use App\Models\Grade;
use App\Models\InstitutionSetting;
use App\Models\Student;
use App\Models\User;
use App\Models\VotingReceipt;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class AdminController extends Controller
{
    public function updateInstitution(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        $setting = InstitutionSetting::query()->firstOrCreate(
            ['id' => 1],
            [
                'school_name' => 'SISVOT Colegio',
                'election_year' => (string) now()->year,
                'configured_once' => false,
            ]
        );

        if ($setting->configured_once || $this->electionProcessStarted()) {
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => 'La configuración institucional ya está bloqueada porque el proceso electoral inició o ya fue configurada una vez.',
            ]);
        }

        $validated = $request->validate([
            'school_name' => ['required', 'string', 'max:255'],
            'election_year' => ['required', 'string', 'max:10'],
            'school_logo' => ['nullable', 'file', 'max:4096'],
        ]);

        $setting->school_name = $validated['school_name'];
        $setting->election_year = $validated['election_year'];

        if ($request->hasFile('school_logo')) {
            $originalName = $request->file('school_logo')->getClientOriginalName();
            $storedName = now()->format('YmdHis').'-'.Str::random(6).'-'.$originalName;
            $setting->school_logo_path = $request->file('school_logo')->storeAs('institution', $storedName, 'public');
        }

        $setting->configured_once = true;
        $setting->save();

        return redirect()->route('admin.dashboard')->with('status', 'Barra institucional configurada y bloqueada correctamente.');
    }

    public function storeJury(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        if ($this->electionProcessStarted()) {
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => 'El proceso electoral ya inició. No se permiten cambios de configuración.',
            ]);
        }

        $validated = $request->validate([
            'jury_name' => ['required', 'string', 'max:255'],
            'jury_email' => ['required', 'email', 'max:255', 'unique:users,email'],
            'jury_password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);

        User::query()->create([
            'name' => $validated['jury_name'],
            'email' => $validated['jury_email'],
            'password' => $validated['jury_password'],
            'role' => 'jury',
            'is_active' => true,
        ]);

        return redirect()->route('admin.dashboard')->with('status', 'Jurado creado correctamente.');
    }

    public function importStudentsCsv(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        if ($this->electionProcessStarted()) {
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => 'El proceso electoral ya inició. No se permiten cambios de configuración.',
            ]);
        }

        $validated = $request->validate([
            'students_csv' => ['required', 'file', 'max:10240'],
        ]);

        $file = $validated['students_csv'];
        $handle = fopen($file->getRealPath(), 'r');
        if (! $handle) {
            return redirect()->route('admin.dashboard')->withErrors(['general' => 'No se pudo leer el archivo CSV.']);
        }

        $header = fgetcsv($handle);
        if (! $header) {
            fclose($handle);
            return redirect()->route('admin.dashboard')->withErrors(['general' => 'El CSV no contiene encabezados.']);
        }

        $normalizedHeader = array_map(fn ($item) => strtolower(trim((string) $item)), $header);
        $required = ['full_name', 'grade_level', 'login_code'];

        foreach ($required as $field) {
            if (! in_array($field, $normalizedHeader, true)) {
                fclose($handle);
                return redirect()->route('admin.dashboard')->withErrors([
                    'general' => "Falta la columna obligatoria '{$field}' en el CSV.",
                ]);
            }
        }

        if (! in_array('pin', $normalizedHeader, true) && ! in_array('login_pin_hash', $normalizedHeader, true)) {
            fclose($handle);
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => "El CSV debe incluir la columna 'pin' o la columna 'login_pin_hash'.",
            ]);
        }

        $indexes = array_flip($normalizedHeader);
        $created = 0;
        $updated = 0;
        $line = 1;
        $errors = [];

        while (($row = fgetcsv($handle)) !== false) {
            $line++;
            if (count(array_filter($row, fn ($value) => trim((string) $value) !== '')) === 0) {
                continue;
            }

            $fullName = trim((string) ($row[$indexes['full_name']] ?? ''));
            $gradeLevel = (int) trim((string) ($row[$indexes['grade_level']] ?? '0'));
            $loginCode = trim((string) ($row[$indexes['login_code']] ?? ''));
            $pin = isset($indexes['pin']) ? trim((string) ($row[$indexes['pin']] ?? '')) : '';
            $loginPinHash = isset($indexes['login_pin_hash']) ? trim((string) ($row[$indexes['login_pin_hash']] ?? '')) : '';
            $documentNumber = isset($indexes['document_number']) ? trim((string) ($row[$indexes['document_number']] ?? '')) : null;

            $storedPinHash = null;
            if ($loginPinHash !== '') {
                $looksHashed = Str::startsWith($loginPinHash, ['$2y$', '$2a$', '$2b$', '$argon2i$', '$argon2id$']);
                if (! $looksHashed) {
                    $errors[] = "Línea {$line}: login_pin_hash no tiene un formato de hash válido.";
                    continue;
                }
                $storedPinHash = $loginPinHash;
            } elseif ($pin !== '') {
                $storedPinHash = Hash::make($pin);
            }

            if ($fullName === '' || $loginCode === '' || $storedPinHash === null || $gradeLevel < 1 || $gradeLevel > 11) {
                $errors[] = "Línea {$line}: datos inválidos (nombre, grado 1-11, login_code y pin o login_pin_hash son obligatorios).";
                continue;
            }

            $grade = Grade::query()->firstOrCreate(
                ['level' => $gradeLevel],
                ['name' => $gradeLevel === 1 ? 'Primero' : "Grado {$gradeLevel}"]
            );

            $student = Student::query()->where('login_code', $loginCode)->first();
            if ($student) {
                $student->forceFill([
                    'grade_id' => $grade->id,
                    'full_name' => $fullName,
                    'document_number' => $documentNumber ?: null,
                    'login_pin_hash' => $storedPinHash,
                    'has_voted' => false,
                    'voted_at' => null,
                ])->save();
                $updated++;
            } else {
                Student::query()->create([
                    'grade_id' => $grade->id,
                    'full_name' => $fullName,
                    'document_number' => $documentNumber ?: null,
                    'login_code' => $loginCode,
                    'login_pin_hash' => $storedPinHash,
                    'has_voted' => false,
                    'voted_at' => null,
                ]);
                $created++;
            }
        }

        fclose($handle);

        $status = "Importación completada. Creados: {$created}. Actualizados: {$updated}.";
        if (count($errors) > 0) {
            $status .= ' Registros con error: '.count($errors).'.';
            return redirect()->route('admin.dashboard')->with('status', $status)->withErrors(['general' => implode(' | ', array_slice($errors, 0, 5))]);
        }

        return redirect()->route('admin.dashboard')->with('status', $status);
    }

    public function downloadStudentsTemplateCsv(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        $fileName = 'plantilla-estudiantes.csv';

        return response()->streamDownload(function () {
            $handle = fopen('php://output', 'w');
            fputcsv($handle, ['full_name', 'grade_level', 'login_code', 'pin', 'login_pin_hash', 'document_number']);
            fputcsv($handle, ['Sofia Diaz', '5', 'STU5001', '1234', '', '10203040']);
            fputcsv($handle, ['Mateo Perez', '11', 'STU1102', '', Hash::make('5678'), '99887766']);
            fclose($handle);
        }, $fileName, [
            'Content-Type' => 'text/csv; charset=UTF-8',
        ]);
    }

    public function storeElection(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        $activeElection = Election::query()->where('is_active', true)->latest('id')->first();
        if ($activeElection) {
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => 'Existe una elección activa. Ciérrala oficialmente para poder crear una nueva y conservar el historial.',
            ]);
        }

        $validated = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'candidate_upload_starts_at' => ['required', 'date'],
            'candidate_upload_ends_at' => ['required', 'date', 'after:candidate_upload_starts_at'],
            'ends_at' => ['required', 'date', 'after:candidate_upload_ends_at'],
        ]);

        Election::query()->create([
            'name' => $validated['name'],
            'candidate_upload_starts_at' => $validated['candidate_upload_starts_at'],
            'candidate_upload_ends_at' => $validated['candidate_upload_ends_at'],
            'starts_at' => $validated['candidate_upload_ends_at'],
            'ends_at' => $validated['ends_at'],
            'voting_started_at' => null,
            'is_active' => true,
        ]);

        Student::query()->update([
            'has_voted' => false,
            'voted_at' => null,
        ]);

        return redirect()->route('admin.dashboard')->with('status', 'Nueva elección creada. Los votantes quedaron habilitados para este nuevo proceso. Inicia la votación desde el botón "Iniciar votación".');
    }

    public function startElectionVoting(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        $election = Election::query()->where('is_active', true)->latest('id')->first();
        if (! $election) {
            return redirect()->route('admin.dashboard')->withErrors(['general' => 'No hay elección activa para iniciar votación.']);
        }

        if ($election->voting_started_at) {
            return redirect()->route('admin.dashboard')->withErrors(['general' => 'La votación ya fue iniciada.']);
        }

        $election->forceFill([
            'starts_at' => now(),
            'voting_started_at' => now(),
        ])->save();

        return redirect()->route('admin.dashboard')->with('status', 'Votación iniciada correctamente.');
    }

    public function dashboard(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        $election = Election::query()->latest('id')->first();
        if (! $election) {
            return view('admin.dashboard', [
                'election' => null,
                'electionsHistory' => Election::query()->latest('id')->get(),
                'grades' => Grade::query()->orderBy('level')->get(),
                'juries' => User::query()->where('role', 'jury')->orderBy('name')->get(),
                'institution' => InstitutionSetting::query()->first(),
                'institutionLocked' => $this->institutionLocked(),
                'personeroResults' => collect(),
                'contralorResults' => collect(),
                'personeroBlank' => 0,
                'contralorBlank' => 0,
                'totalBallots' => 0,
                'eligibleVoters' => 0,
                'turnoutPercent' => 0,
                'receiptsCount' => 0,
                'recentReceipts' => collect(),
                'revealWinners' => false,
                'personeroWinner' => null,
                'contralorWinner' => null,
            ]);
        }

        return view('admin.dashboard', array_merge(
            $this->buildScrutinyData($election),
            [
                'electionsHistory' => Election::query()->latest('id')->get(),
                'grades' => Grade::query()->orderBy('level')->get(),
                'juries' => User::query()->where('role', 'jury')->orderBy('name')->get(),
                'institution' => InstitutionSetting::query()->first(),
                'institutionLocked' => $this->institutionLocked(),
            ]
        ));
    }

    public function storeCandidate(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        if ($this->electionProcessStarted()) {
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => 'El proceso electoral ya inició. No se permiten cambios de configuración.',
            ]);
        }

        $election = Election::query()->where('is_active', true)->latest('id')->first();
        if (! $election) {
            return redirect()->route('admin.dashboard')->withErrors(['general' => 'No hay elección activa para registrar candidatos.']);
        }

        if (! $election->canUploadCandidates()) {
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => 'El registro de candidatos no está habilitado en este rango de fechas.',
            ]);
        }

        if (Ballot::query()->where('election_id', $election->id)->exists()) {
            return redirect()->route('admin.dashboard')->withErrors([
                'general' => 'No se pueden registrar o modificar candidatos después de iniciar la votación.',
            ]);
        }

        $validated = $request->validate([
            'role' => ['required', 'in:personero,contralor'],
            'display_number' => ['required', 'integer', 'min:1', 'max:99'],
            'grade_id' => ['required', 'exists:grades,id'],
            'full_name' => ['required', 'string', 'max:255'],
            'proposal' => ['nullable', 'string', 'max:1000'],
            'photo' => ['required', 'file', 'max:4096'],
        ]);

        $originalName = $request->file('photo')->getClientOriginalName();
        $storedName = now()->format('YmdHis').'-'.Str::random(6).'-'.$originalName;
        $photoPath = $request->file('photo')->storeAs('candidates', $storedName, 'public');

        Candidate::query()->updateOrCreate(
            [
                'election_id' => $election->id,
                'role' => $validated['role'],
                'display_number' => $validated['display_number'],
            ],
            [
                'grade_id' => $validated['grade_id'],
                'full_name' => $validated['full_name'],
                'proposal' => $validated['proposal'] ?? null,
                'photo_path' => $photoPath,
                'is_active' => true,
            ]
        );

        return redirect()->route('admin.dashboard')->with('status', 'Candidato registrado correctamente.');
    }

    public function exportPdf(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        $election = Election::query()->latest('id')->first();
        if (! $election) {
            return redirect()->route('admin.dashboard')->withErrors(['general' => 'No existe elección para exportar.']);
        }

        $data = $this->buildScrutinyData($election);
        $pdf = Pdf::loadView('admin.scrutiny-pdf', $data);

        return $pdf->download('acta-escrutinio-'.$election->id.'.pdf');
    }

    public function closeElection(Request $request)
    {
        $adminRedirect = $this->ensureAdmin($request);
        if ($adminRedirect) {
            return $adminRedirect;
        }

        $election = Election::query()->where('is_active', true)->latest('id')->first();
        if (! $election) {
            return redirect()->route('admin.dashboard')->withErrors(['general' => 'No hay elección activa para cerrar.']);
        }

        $election->forceFill([
            'is_active' => false,
            'ends_at' => now(),
        ])->save();

        return redirect()->route('admin.dashboard')->with('status', 'Elección cerrada oficialmente. Se bloqueó la votación y ya puede declararse ganador.');
    }

    private function ensureAdmin(Request $request)
    {
        $adminUserId = $request->session()->get('admin_user_id');
        if (! $adminUserId) {
            return redirect()->route('admin.login.form');
        }

        $admin = User::query()->where('id', $adminUserId)->where('role', 'admin')->where('is_active', true)->first();
        if (! $admin) {
            return redirect()->route('admin.login.form');
        }

        return null;
    }

    private function electionProcessStarted(): bool
    {
        $activeElection = Election::query()->where('is_active', true)->latest('id')->first();
        if (! $activeElection) {
            return false;
        }

        return $activeElection->voting_started_at !== null || Ballot::query()->where('election_id', $activeElection->id)->exists();
    }

    private function institutionLocked(): bool
    {
        $setting = InstitutionSetting::query()->first();
        if (! $setting) {
            return $this->electionProcessStarted();
        }

        return $setting->configured_once || $this->electionProcessStarted();
    }

    public static function buildScrutinyData(Election $election): array
    {
        $personeroResults = Candidate::query()
            ->where('election_id', $election->id)
            ->where('role', 'personero')
            ->where('is_active', true)
            ->orderBy('display_number')
            ->get()
            ->map(function (Candidate $candidate) use ($election) {
                $votes = Ballot::query()
                    ->where('election_id', $election->id)
                    ->where('personero_candidate_id', $candidate->id)
                    ->count();

                $candidate->votes_count = $votes;
                return $candidate;
            })
            ->sortByDesc('votes_count')
            ->values();

        $contralorResults = Candidate::query()
            ->where('election_id', $election->id)
            ->where('role', 'contralor')
            ->where('is_active', true)
            ->orderBy('display_number')
            ->get()
            ->map(function (Candidate $candidate) use ($election) {
                $votes = Ballot::query()
                    ->where('election_id', $election->id)
                    ->where('contralor_candidate_id', $candidate->id)
                    ->count();

                $candidate->votes_count = $votes;
                return $candidate;
            })
            ->sortByDesc('votes_count')
            ->values();

        $personeroBlank = Ballot::query()->where('election_id', $election->id)->where('personero_blank', true)->count();
        $contralorBlank = Ballot::query()->where('election_id', $election->id)->where('contralor_blank', true)->count();
        $totalBallots = Ballot::query()->where('election_id', $election->id)->count();
        $eligibleVoters = Student::query()->count();
        $turnoutPercent = $eligibleVoters > 0 ? round(($totalBallots * 100) / $eligibleVoters, 2) : 0;
        $receiptsCount = VotingReceipt::query()->where('election_id', $election->id)->count();
        $recentReceipts = VotingReceipt::query()->where('election_id', $election->id)->latest('id')->take(20)->get(['receipt_code', 'voted_at']);

        $revealWinners = ! $election->is_active;
        $personeroWinner = $revealWinners ? $personeroResults->sortByDesc('votes_count')->first() : null;
        $contralorWinner = $revealWinners ? $contralorResults->sortByDesc('votes_count')->first() : null;

        $personeroResults = $personeroResults->map(function (Candidate $candidate) use ($totalBallots) {
            $candidate->percent = $totalBallots > 0 ? round(($candidate->votes_count * 100) / $totalBallots, 2) : 0;
            return $candidate;
        });

        $contralorResults = $contralorResults->map(function (Candidate $candidate) use ($totalBallots) {
            $candidate->percent = $totalBallots > 0 ? round(($candidate->votes_count * 100) / $totalBallots, 2) : 0;
            return $candidate;
        });

        return compact(
            'election',
            'personeroResults',
            'contralorResults',
            'personeroBlank',
            'contralorBlank',
            'totalBallots',
            'eligibleVoters',
            'turnoutPercent',
            'receiptsCount',
            'recentReceipts',
            'revealWinners',
            'personeroWinner',
            'contralorWinner'
        );
    }
}
