<?php
/**
 * Voting system helper functions
 */

/**
 * Check if voting system is enabled
 */
function isVotingEnabled() {
    global $pdo;
    require_once __DIR__ . '/settings.php';
    return getSetting('voting_enabled', '0') === '1';
}

/**
 * Require voting system to be enabled
 */
function requireVotingEnabled() {
    if (!isVotingEnabled()) {
        // Only redirect if we're not already showing an error to avoid redirect loops
        $currentUri = $_SERVER['REQUEST_URI'] ?? '';
        if (strpos($currentUri, 'toast_error') === false) {
            header('Location: ' . BASE_PATH . '/voting?toast_error=' . urlencode('Voting system is currently disabled. Please contact an administrator.'));
            exit;
        }
    }
}

/**
 * Get all elections
 */
function getAllElections($status = null, $enabledOnly = false) {
    global $pdo;
    $sql = "SELECT * FROM elections ORDER BY created_at DESC";
    $params = [];
    $where = [];
    if ($status) {
        $where[] = "status = ?";
        $params[] = $status;
    }
    if ($enabledOnly) {
        $where[] = "enabled = 1";
    }
    if (!empty($where)) {
        $sql = "SELECT * FROM elections WHERE " . implode(' AND ', $where) . " ORDER BY created_at DESC";
    }
    if ($params) {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }
    return $pdo->query($sql)->fetchAll();
}

/**
 * Get elections visible to the current user: admin sees all; voting user sees only those they manage.
 */
function getElectionsForUser($userId, $isAdmin = false) {
    global $pdo;
    if ($isAdmin) {
        return $pdo->query("SELECT * FROM elections ORDER BY created_at DESC")->fetchAll();
    }
    $stmt = $pdo->prepare("SELECT * FROM elections WHERE enabled = 1 AND manager_user_id = ? ORDER BY created_at DESC");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}

/**
 * Get election by ID.
 * If status is 'active' and end_date has passed, auto-closes the election and returns updated row.
 */
function getElection($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM elections WHERE id = ?");
    $stmt->execute([$id]);
    $election = $stmt->fetch();
    if (!$election) {
        return null;
    }
    // Auto-close when end date/time has passed
    if (($election['status'] ?? '') === 'active' && !empty($election['end_date'])) {
        $endTs = strtotime($election['end_date']);
        if ($endTs !== false && time() >= $endTs) {
            try {
                $pdo->prepare("UPDATE elections SET status = 'closed' WHERE id = ? AND status = 'active'")->execute([$id]);
                $election['status'] = 'closed';
            } catch (PDOException $e) {
                // If update fails, still treat as closed for this request
                $election['status'] = 'closed';
            }
        }
    }
    return $election;
}

/**
 * Check if current user can manage this election (admin or assigned manager)
 */
function canManageElection($electionId) {
    $election = getElection($electionId);
    if (!$election) return false;
    if (isAdmin()) return true;
    $managerId = (int)($election['manager_user_id'] ?? 0);
    return $managerId > 0 && $managerId === (int)currentUserId();
}

/**
 * Get positions for an election
 */
function getElectionPositions($electionId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM election_positions WHERE election_id = ? ORDER BY position_order, name");
    $stmt->execute([$electionId]);
    return $stmt->fetchAll();
}

/**
 * Get candidates for a position
 */
function getPositionCandidates($positionId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT ec.*, m.id as member_id, m.title, m.first_name, m.middle_name, m.last_name, m.photo, m.gender
        FROM election_candidates ec
        JOIN members m ON ec.member_id = m.id
        WHERE ec.position_id = ? AND (m.status != 'deceased' OR m.status IS NULL) AND (m.date_of_death IS NULL)
        ORDER BY m.last_name, m.first_name
    ");
    $stmt->execute([$positionId]);
    return $stmt->fetchAll();
}

/**
 * Get candidates for a presbyters election (by gender)
 */
function getPresbytersCandidates($electionId, $gender = null) {
    global $pdo;
    $sql = "
        SELECT ec.*, m.id as member_id, m.title, m.first_name, m.middle_name, m.last_name, m.photo, m.gender
        FROM election_candidates ec
        JOIN members m ON ec.member_id = m.id
        WHERE ec.election_id = ? AND ec.position_id IS NULL 
        AND (m.status != 'deceased' OR m.status IS NULL) AND (m.date_of_death IS NULL)
    ";
    if ($gender) {
        $sql .= " AND m.gender = ?";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$electionId, $gender]);
    } else {
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$electionId]);
    }
    return $stmt->fetchAll();
}

/**
 * Check if member has voted in an election
 */
function hasMemberVoted($electionId, $memberId) {
    global $pdo;
    $election = getElection($electionId);
    if (!$election) return false;
    
    if ($election['type'] === 'fellowship') {
        // Check if member has voted for all positions
        $positions = getElectionPositions($electionId);
        foreach ($positions as $pos) {
            $stmt = $pdo->prepare("SELECT id FROM election_votes WHERE election_id = ? AND voter_member_id = ? AND position_id = ?");
            $stmt->execute([$electionId, $memberId, $pos['id']]);
            if (!$stmt->fetch()) {
                return false; // Hasn't voted for this position
            }
        }
        return true;
    } else {
        // Presbyters election - check if member has voted
        $stmt = $pdo->prepare("SELECT id FROM election_votes WHERE election_id = ? AND voter_member_id = ? LIMIT 1");
        $stmt->execute([$electionId, $memberId]);
        return (bool) $stmt->fetch();
    }
}

/**
 * Get vote count for a candidate
 */
function getCandidateVoteCount($candidateId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) as count FROM election_votes WHERE candidate_id = ?");
    $stmt->execute([$candidateId]);
    $row = $stmt->fetch();
    return (int) ($row['count'] ?? 0);
}

/**
 * Get election results
 */
function getElectionResults($electionId) {
    global $pdo;
    $election = getElection($electionId);
    if (!$election) return null;
    
    if ($election['type'] === 'fellowship') {
        $positions = getElectionPositions($electionId);
        $results = [];
        foreach ($positions as $pos) {
            $candidates = getPositionCandidates($pos['id']);
            $candidateResults = [];
            foreach ($candidates as $cand) {
                $candidateResults[] = [
                    'candidate' => $cand,
                    'votes' => getCandidateVoteCount($cand['id'])
                ];
            }
            usort($candidateResults, function($a, $b) {
                return $b['votes'] - $a['votes'];
            });
            $results[] = [
                'position' => $pos,
                'candidates' => $candidateResults
            ];
        }
        return $results;
    } else {
        // Presbyters election
        $femaleCandidates = getPresbytersCandidates($electionId, 'Female');
        $maleCandidates = getPresbytersCandidates($electionId, 'Male');
        
        $femaleResults = [];
        foreach ($femaleCandidates as $cand) {
            $femaleResults[] = [
                'candidate' => $cand,
                'votes' => getCandidateVoteCount($cand['id'])
            ];
        }
        usort($femaleResults, function($a, $b) {
            return $b['votes'] - $a['votes'];
        });
        
        $maleResults = [];
        foreach ($maleCandidates as $cand) {
            $maleResults[] = [
                'candidate' => $cand,
                'votes' => getCandidateVoteCount($cand['id'])
            ];
        }
        usort($maleResults, function($a, $b) {
            return $b['votes'] - $a['votes'];
        });
        
        return [
            'females' => $femaleResults,
            'males' => $maleResults
        ];
    }
}

// ─── Eligible Voter List ─────────────────────────────────────────────────────

/**
 * Return all eligible voters for an election, joined with member data.
 */
function getElectionVoters($electionId) {
    global $pdo;
    try {
        $stmt = $pdo->prepare("
            SELECT ev.id as voter_id, m.id as member_id,
                   m.first_name, m.middle_name, m.last_name, m.photo,
                   m.gender, m.date_of_birth, m.status as member_status
            FROM election_voters ev
            JOIN members m ON m.id = ev.member_id
            WHERE ev.election_id = ?
            ORDER BY m.last_name, m.first_name
        ");
        $stmt->execute([$electionId]);
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

/**
 * Check if a member is on the eligible voter list for an election.
 * Returns true when the election_voters table has no rows for this election
 * (legacy elections before this feature was added) so they remain open.
 */
function isVoterEligible($electionId, $memberId) {
    global $pdo;
    try {
        $count = $pdo->prepare("SELECT COUNT(*) FROM election_voters WHERE election_id = ?");
        $count->execute([$electionId]);
        if ((int)$count->fetchColumn() === 0) {
            return true; // No voter list yet — open to all (backwards-compatible)
        }
        $stmt = $pdo->prepare("SELECT id FROM election_voters WHERE election_id = ? AND member_id = ?");
        $stmt->execute([$electionId, $memberId]);
        return (bool)$stmt->fetch();
    } catch (PDOException $e) {
        return true;
    }
}

/**
 * Whether an election has an explicit voter list configured.
 */
function electionHasVoterList($electionId) {
    global $pdo;
    try {
        $stmt = $pdo->prepare("SELECT COUNT(*) FROM election_voters WHERE election_id = ?");
        $stmt->execute([$electionId]);
        return (int)$stmt->fetchColumn() > 0;
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Add a member to the eligible voter list.
 */
function addVoterToElection($electionId, $memberId, $addedBy = null) {
    global $pdo;
    try {
        $stmt = $pdo->prepare("INSERT IGNORE INTO election_voters (election_id, member_id, added_by) VALUES (?, ?, ?)");
        $stmt->execute([$electionId, $memberId, $addedBy]);
        return true;
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Remove a member from the eligible voter list.
 */
function removeVoterFromElection($electionId, $memberId) {
    global $pdo;
    try {
        $pdo->prepare("DELETE FROM election_voters WHERE election_id = ? AND member_id = ?")
            ->execute([$electionId, $memberId]);
        return true;
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Auto-populate voter list.
 * For presbyters elections: all active members aged 18+ (and those with no DOB on file).
 * For fellowship elections: all active members of the assigned group.
 * Returns number of voters added.
 */
function autoPopulateVoters($electionId, $addedBy = null) {
    global $pdo;
    $election = getElection($electionId);
    if (!$election) return 0;

    if ($election['type'] === 'presbyters') {
        // 18+ active members (exclude deceased; include those with no DOB)
        $stmt = $pdo->query("
            SELECT id FROM members
            WHERE (status != 'deceased' OR status IS NULL) AND date_of_death IS NULL
              AND (
                date_of_birth IS NULL
                OR TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE()) >= 18
              )
        ");
    } else {
        // Fellowship election: members of the assigned group
        $groupType = $election['group_type'] ?? null;
        $groupId   = $election['group_id']   ?? null;
        $groupName = $election['group_name'] ?? null;

        if ($groupType === 'service_grp' && $groupId) {
            $stmt = $pdo->prepare("
                SELECT DISTINCT m.id FROM members m
                JOIN member_ministries mm ON mm.member_id = m.id
                WHERE mm.ministry_id = ?
                  AND (m.status != 'deceased' OR m.status IS NULL)
                  AND m.date_of_death IS NULL
            ");
            $stmt->execute([$groupId]);
        } elseif ($groupType === 'gen_grp' && $groupName) {
            // Map fellowship name → age/gender SQL conditions
            $base = "(status != 'deceased' OR status IS NULL) AND date_of_death IS NULL AND date_of_birth IS NOT NULL";
            $ageExpr = "TIMESTAMPDIFF(YEAR, date_of_birth, CURDATE())";
            switch ($groupName) {
                case 'YPG':
                    $where = "$base AND $ageExpr >= 18 AND $ageExpr < 30";
                    break;
                case 'YAF':
                    $where = "$base AND $ageExpr >= 30 AND $ageExpr < 40";
                    break;
                case "Men's Fellowship":
                    $where = "$base AND $ageExpr >= 40 AND UPPER(TRIM(gender)) IN ('MALE','M')";
                    break;
                case "Women's Fellowship":
                    $where = "$base AND $ageExpr >= 40 AND UPPER(TRIM(gender)) IN ('FEMALE','F')";
                    break;
                case 'Junior Youth':
                    $where = "$base AND $ageExpr >= 12 AND $ageExpr < 18";
                    break;
                case "Children's Service":
                    $where = "$base AND $ageExpr < 12";
                    break;
                default:
                    // Unknown group name — fall back to all active
                    $where = "(status != 'deceased' OR status IS NULL) AND date_of_death IS NULL";
            }
            $stmt = $pdo->query("SELECT id FROM members WHERE $where");
        } else {
            // No group assigned — use all active members
            $stmt = $pdo->query("
                SELECT id FROM members
                WHERE (status != 'deceased' OR status IS NULL) AND date_of_death IS NULL
            ");
        }
    }

    $members = $stmt->fetchAll(PDO::FETCH_COLUMN);
    $added = 0;
    foreach ($members as $memberId) {
        if (addVoterToElection($electionId, $memberId, $addedBy)) {
            $added++;
        }
    }
    return $added;
}

/**
 * Clear the entire voter list for an election (allows re-population).
 */
function clearVoterList($electionId) {
    global $pdo;
    try {
        $pdo->prepare("DELETE FROM election_voters WHERE election_id = ?")->execute([$electionId]);
        return true;
    } catch (PDOException $e) {
        return false;
    }
}

// ─── Check-in ────────────────────────────────────────────────────────────────

/**
 * Check if a member has been checked in for an election.
 */
function hasMemberCheckedIn($electionId, $memberId) {
    global $pdo;
    try {
        $stmt = $pdo->prepare("SELECT id FROM election_checkins WHERE election_id = ? AND member_id = ?");
        $stmt->execute([$electionId, $memberId]);
        return (bool)$stmt->fetch();
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Generate a unique 5-character alphanumeric PIN for a given election.
 * Uses characters that are visually unambiguous (no 0/O/I/1).
 * Returns the PIN string, or null on failure after many attempts.
 */
function generateUniquePin($electionId) {
    global $pdo;
    $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $len   = strlen($chars);
    for ($attempt = 0; $attempt < 30; $attempt++) {
        $pin = '';
        for ($i = 0; $i < 5; $i++) {
            $pin .= $chars[random_int(0, $len - 1)];
        }
        $chk = $pdo->prepare("SELECT id FROM election_checkins WHERE election_id = ? AND pin_code = ?");
        $chk->execute([$electionId, $pin]);
        if (!$chk->fetch()) {
            return $pin;
        }
    }
    return null;
}

/**
 * Record a check-in for a member and issue a voting PIN.
 * Returns the PIN string on success (existing PIN if already checked in), or false on failure.
 */
function checkInMember($electionId, $memberId, $checkedInBy = null) {
    global $pdo;
    try {
        // If already checked in, return the existing PIN
        $existing = $pdo->prepare("SELECT pin_code FROM election_checkins WHERE election_id = ? AND member_id = ?");
        $existing->execute([$electionId, $memberId]);
        $row = $existing->fetch();
        if ($row) {
            return $row['pin_code'];
        }
        $pin  = generateUniquePin($electionId);
        $stmt = $pdo->prepare("INSERT INTO election_checkins (election_id, member_id, checked_in_by, pin_code) VALUES (?, ?, ?, ?)");
        $stmt->execute([$electionId, $memberId, $checkedInBy, $pin]);
        return $pin;
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Look up a voter by their PIN for a given election.
 * Returns the member row (with check-in data) or false if PIN is invalid/already used.
 */
function getVoterByPin($electionId, $pin) {
    global $pdo;
    $pin = strtoupper(trim($pin));
    if (strlen($pin) !== 5) return false;
    try {
        $stmt = $pdo->prepare("
            SELECT ec.member_id, ec.pin_used,
                   m.first_name, m.middle_name, m.last_name,
                   m.photo, m.gender, m.date_of_birth, m.status as member_status
            FROM election_checkins ec
            JOIN members m ON m.id = ec.member_id
            WHERE ec.election_id = ? AND ec.pin_code = ?
        ");
        $stmt->execute([$electionId, $pin]);
        return $stmt->fetch() ?: false;
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Mark the PIN as used after a vote is successfully cast.
 */
function markPinUsed($electionId, $memberId) {
    global $pdo;
    try {
        $pdo->prepare("UPDATE election_checkins SET pin_used = 1 WHERE election_id = ? AND member_id = ?")
            ->execute([$electionId, $memberId]);
        return true;
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Undo a check-in (e.g. if done in error).
 */
function undoCheckIn($electionId, $memberId) {
    global $pdo;
    try {
        $pdo->prepare("DELETE FROM election_checkins WHERE election_id = ? AND member_id = ?")
            ->execute([$electionId, $memberId]);
        return true;
    } catch (PDOException $e) {
        return false;
    }
}

/**
 * Return the voting status for a single member in an election.
 * 'not_eligible' | 'eligible' | 'checked_in' | 'voted'
 */
function getMemberVoterStatus($electionId, $memberId) {
    if (!isVoterEligible($electionId, $memberId)) return 'not_eligible';
    if (hasMemberVoted($electionId, $memberId))    return 'voted';
    if (hasMemberCheckedIn($electionId, $memberId)) return 'checked_in';
    return 'eligible';
}

/**
 * Return live statistics for an election's voter progress.
 */
function getVoterStats($electionId) {
    global $pdo;
    $stats = [
        'eligible'        => 0,
        'checked_in'      => 0,
        'voted'           => 0,
        'pending_checkin' => 0,
        'checkin_not_voted' => 0,
    ];
    try {
        $r = $pdo->prepare("SELECT COUNT(*) FROM election_voters WHERE election_id = ?");
        $r->execute([$electionId]);
        $stats['eligible'] = (int)$r->fetchColumn();

        $r = $pdo->prepare("SELECT COUNT(*) FROM election_checkins WHERE election_id = ?");
        $r->execute([$electionId]);
        $stats['checked_in'] = (int)$r->fetchColumn();

        $r = $pdo->prepare("SELECT COUNT(DISTINCT voter_member_id) FROM election_votes WHERE election_id = ?");
        $r->execute([$electionId]);
        $stats['voted'] = (int)$r->fetchColumn();

        $stats['pending_checkin']   = max(0, $stats['eligible'] - $stats['checked_in']);
        $stats['checkin_not_voted'] = max(0, $stats['checked_in'] - $stats['voted']);
    } catch (PDOException $e) {
        // Tables may not exist in older installs
    }
    return $stats;
}

// ─── Total votes (fixed) ──────────────────────────────────────────────────────

/**
 * Get total votes cast in an election
 */
function getTotalVotesCast($electionId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(DISTINCT voter_member_id) FROM election_votes WHERE election_id = ?");
    $stmt->execute([$electionId]);
    return (int)$stmt->fetchColumn();
}
