<?php
require __DIR__ . '/bootstrap.php';
require __DIR__ . '/_walk_access.php';

use App\Core\Auth;
use App\Core\Csrf;
use App\Core\Database;
use App\Core\Response;
use App\Core\Security;

$pdo = Database::connection();
$input = request_input();
$method = $_SERVER['REQUEST_METHOD'];
$action = $input['action'] ?? ($method === 'GET' ? 'active' : '');

function pyrk_polls_can_manage(?array $user): bool
{
    if (!$user) return false;
    return Auth::hasPermission('polls.manage', $user);
}

function pyrk_polls_require_manager(?array $user): void
{
    if (!pyrk_polls_can_manage($user)) {
        Response::error('You do not have permission to manage polls.', 403);
    }
}

function pyrk_poll_date_window(mixed $value, bool $endOfDay = false): ?string
{
    $value = trim((string)$value);
    if ($value === '') return null;

    if (preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $value, $m)) {
        $day = (int)$m[1];
        $month = (int)$m[2];
        $year = (int)$m[3];

        if (!checkdate($month, $day, $year)) {
            Response::error('Invalid date. Use dd/mm/yyyy.');
        }

        return sprintf('%04d-%02d-%02d %s', $year, $month, $day, $endOfDay ? '23:59:59' : '00:00:00');
    }

    return str_replace('T', ' ', $value);
}

function pyrk_poll_member_ids_from_input(array $input): array
{
    $ids = $input['member_ids'] ?? [];

    if (is_string($ids)) {
        $decoded = json_decode($ids, true);
        if (is_array($decoded)) {
            $ids = $decoded;
        } else {
            $ids = preg_split('/\s*,\s*/', $ids);
        }
    }

    if (!is_array($ids)) $ids = [];
    return array_values(array_unique(array_filter(array_map('intval', $ids))));
}

function pyrk_poll_group_ids_from_input(array $input): array
{
    $ids = $input['group_ids'] ?? [];

    if (is_string($ids)) {
        $decoded = json_decode($ids, true);
        if (is_array($decoded)) {
            $ids = $decoded;
        } else {
            $ids = preg_split('/\s*,\s*/', $ids);
        }
    }

    if (!is_array($ids)) $ids = [];
    return array_values(array_unique(array_filter(array_map('intval', $ids))));
}

function pyrk_poll_group_list(PDO $pdo): array
{
    try {
        $stmt = $pdo->query('SELECT g.id, g.group_name, g.description, g.status,
            (SELECT COUNT(*) FROM member_group_members mgm WHERE mgm.group_id = g.id) AS member_count
            FROM member_groups g
            ORDER BY g.status = "active" DESC, g.group_name ASC');
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (Throwable) {
        return [];
    }
}

function pyrk_poll_target_groups(PDO $pdo, array $pollIds): array
{
    if (!$pollIds) return [];
    $placeholders = implode(',', array_fill(0, count($pollIds), '?'));
    $stmt = $pdo->prepare("SELECT ptg.poll_id, ptg.group_id, g.group_name, g.status,
            (SELECT COUNT(*) FROM member_group_members mgm WHERE mgm.group_id = g.id) AS member_count
        FROM poll_target_groups ptg
        JOIN member_groups g ON g.id = ptg.group_id
        WHERE ptg.poll_id IN ($placeholders)
        ORDER BY g.group_name ASC");
    $stmt->execute($pollIds);
    $out = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $out[(int)$row['poll_id']][] = $row;
    }
    return $out;
}

function pyrk_poll_member_list(PDO $pdo): array
{
    $stmt = $pdo->query('SELECT u.id, u.full_name, u.email, u.mobile, u.status
        FROM users u
        JOIN roles r ON r.id = u.role_id
        WHERE r.name = "member"
        ORDER BY u.full_name ASC');
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

function pyrk_poll_find(PDO $pdo, int $pollId): ?array
{
    $stmt = $pdo->prepare('SELECT p.*, w.title AS walk_title, w.walk_date
        FROM polls p
        LEFT JOIN walks w ON w.id = p.walk_id
        WHERE p.id = ? LIMIT 1');
    $stmt->execute([$pollId]);
    $poll = $stmt->fetch(PDO::FETCH_ASSOC);
    return $poll ?: null;
}

function pyrk_poll_public_url(int $pollId): string
{
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $scriptDir = rtrim(str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'] ?? '/api')), '/');
    $base = preg_replace('~/api$~', '', $scriptDir) ?: '';
    return $scheme . '://' . $host . $base . '/public/polls.php?id=' . $pollId;
}

function pyrk_poll_email_recipients(PDO $pdo, array $poll): array
{
    $scope = $poll['target_scope'] ?? 'all_members';
    $pollId = (int)($poll['id'] ?? 0);
    $params = [];

    if ($scope === 'selected_members') {
        $sql = 'SELECT DISTINCT u.id, u.full_name, u.email
            FROM poll_target_members ptm
            JOIN users u ON u.id = ptm.user_id
            JOIN roles r ON r.id = u.role_id
            WHERE ptm.poll_id = ? AND r.name = "member" AND u.status = "active" AND u.email IS NOT NULL AND u.email <> ""
            ORDER BY u.full_name ASC';
        $params = [$pollId];
    } elseif ($scope === 'selected_groups') {
        $sql = 'SELECT DISTINCT u.id, u.full_name, u.email
            FROM poll_target_groups ptg
            JOIN member_group_members mgm ON mgm.group_id = ptg.group_id
            JOIN member_groups g ON g.id = ptg.group_id
            JOIN users u ON u.id = mgm.user_id
            JOIN roles r ON r.id = u.role_id
            WHERE ptg.poll_id = ? AND g.status = "active" AND r.name = "member" AND u.status = "active" AND u.email IS NOT NULL AND u.email <> ""
            ORDER BY u.full_name ASC';
        $params = [$pollId];
    } elseif ($scope === 'walk_registrants') {
        if (!pyrk_poll_walk_is_visible($pdo, (int)($poll['walk_id'] ?? 0))) {
            return [];
        }
        $sql = 'SELECT DISTINCT u.id, u.full_name, u.email
            FROM walk_registrations wr
            JOIN users u ON u.id = wr.user_id
            JOIN roles r ON r.id = u.role_id
            WHERE wr.walk_id = ? AND wr.registration_status IN ("registered","approved") AND r.name = "member" AND u.status = "active" AND u.email IS NOT NULL AND u.email <> ""
            ORDER BY u.full_name ASC';
        $params = [(int)($poll['walk_id'] ?? 0)];
    } else {
        $sql = 'SELECT DISTINCT u.id, u.full_name, u.email
            FROM users u
            JOIN roles r ON r.id = u.role_id
            WHERE r.name = "member" AND u.status = "active" AND u.email IS NOT NULL AND u.email <> ""
            ORDER BY u.full_name ASC';
    }

    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $recipients = [];
    foreach ($rows as $row) {
        $email = trim((string)($row['email'] ?? ''));
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) continue;
        $recipients[$email] = [
            'user_id' => (int)$row['id'],
            'name' => (string)($row['full_name'] ?: 'Pyrk member'),
            'email' => $email,
        ];
    }

    return array_values($recipients);
}

function pyrk_poll_last_email_batch(PDO $pdo, int $pollId): ?array
{
    try {
        $stmt = $pdo->prepare('SELECT * FROM poll_email_batches WHERE poll_id = ? ORDER BY created_at DESC, id DESC LIMIT 1');
        $stmt->execute([$pollId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: null;
    } catch (Throwable) {
        return null;
    }
}

function pyrk_poll_email_subject(array $poll): string
{
    return 'Pyrk poll: ' . (string)($poll['title'] ?? 'Member opinion');
}

function pyrk_poll_email_html(array $poll, array $recipient, string $subject, string $message, string $link): string
{
    $e = static fn($v) => htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8');
    $title = $e($poll['title'] ?? 'Member poll');
    $description = trim((string)($poll['description'] ?? ''));
    $ends = !empty($poll['ends_at']) ? '<p><strong>Closes:</strong> ' . $e($poll['ends_at']) . '</p>' : '';
    $custom = trim($message) !== '' ? '<p>' . nl2br($e($message)) . '</p>' : '<p>Please take a minute to share your opinion.</p>';

    return '<p>Hi ' . $e($recipient['name'] ?? 'Pyrk member') . ',</p>'
        . $custom
        . '<p><strong>' . $title . '</strong></p>'
        . ($description !== '' ? '<p>' . nl2br($e($description)) . '</p>' : '')
        . $ends
        . '<p style="margin:24px 0;"><a href="' . $e($link) . '" style="display:inline-block;padding:13px 18px;border-radius:14px;background:#0f7f55;color:#fff;text-decoration:none;font-weight:800;">Vote now</a></p>'
        . '<p>If the button does not open, copy this link:<br><span style="word-break:break-all;color:#64748b;">' . $e($link) . '</span></p>';
}

function pyrk_poll_send_email_to_member(int $userId, string $toEmail, string $toName, string $subject, string $html): array
{
    if (class_exists('\\App\\Services\\NotificationService')) {
        try {
            $svc = new \App\Services\NotificationService();
            return $svc->sendEmail($userId, null, $toEmail, $toName, $subject, $html, strip_tags($html));
        } catch (Throwable $e) {
            return ['status' => 'failed', 'error' => $e->getMessage()];
        }
    }

    try {
        $mail = $GLOBALS['config']['mail'] ?? [];
        $driver = $mail['driver'] ?? 'log';
        if ($driver === 'mail') {
            $fromEmail = $mail['from_email'] ?? 'club@pyrk.local';
            $fromName = $mail['from_name'] ?? 'Pyrk';
            $headers = "MIME-Version: 1.0\r\nContent-Type: text/html; charset=UTF-8\r\n";
            $headers .= 'From: ' . $fromName . ' <' . $fromEmail . ">\r\n";
            if (!mail($toEmail, $subject, $html, $headers)) {
                throw new RuntimeException('PHP mail() returned false.');
            }
            return ['status' => 'sent', 'error' => null];
        }

        $dir = __DIR__ . '/../storage/logs';
        if (!is_dir($dir)) @mkdir($dir, 0775, true);
        @file_put_contents($dir . '/poll_emails.log', '[' . date('c') . '] to ' . $toEmail . ' | ' . $subject . "\n" . strip_tags($html) . "\n\n", FILE_APPEND);
        return ['status' => 'logged', 'error' => null];
    } catch (Throwable $e) {
        return ['status' => 'failed', 'error' => $e->getMessage()];
    }
}

function pyrk_poll_options(PDO $pdo, array $pollIds): array
{
    if (!$pollIds) return [];
    $placeholders = implode(',', array_fill(0, count($pollIds), '?'));
    $stmt = $pdo->prepare("SELECT * FROM poll_options WHERE poll_id IN ($placeholders) ORDER BY poll_id ASC, sort_order ASC, id ASC");
    $stmt->execute($pollIds);
    $out = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $out[(int)$row['poll_id']][] = $row;
    }
    return $out;
}

function pyrk_poll_target_members(PDO $pdo, array $pollIds): array
{
    if (!$pollIds) return [];
    $placeholders = implode(',', array_fill(0, count($pollIds), '?'));
    $stmt = $pdo->prepare("SELECT ptm.poll_id, ptm.user_id, u.full_name, u.email, u.mobile
        FROM poll_target_members ptm
        JOIN users u ON u.id = ptm.user_id
        WHERE ptm.poll_id IN ($placeholders)
        ORDER BY u.full_name ASC");
    $stmt->execute($pollIds);
    $out = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $out[(int)$row['poll_id']][] = $row;
    }
    return $out;
}

function pyrk_poll_result_summary(PDO $pdo, array $pollIds): array
{
    if (!$pollIds) return [];
    $placeholders = implode(',', array_fill(0, count($pollIds), '?'));
    $stmt = $pdo->prepare("SELECT po.poll_id, po.id AS option_id, po.option_text, COUNT(pro.response_id) AS votes
        FROM poll_options po
        LEFT JOIN poll_response_options pro ON pro.option_id = po.id
        WHERE po.poll_id IN ($placeholders)
        GROUP BY po.poll_id, po.id, po.option_text, po.sort_order
        ORDER BY po.poll_id ASC, po.sort_order ASC, po.id ASC");
    $stmt->execute($pollIds);
    $out = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $out[(int)$row['poll_id']][] = [
            'option_id' => (int)$row['option_id'],
            'option_text' => $row['option_text'],
            'votes' => (int)$row['votes'],
        ];
    }
    return $out;
}

function pyrk_poll_my_responses(PDO $pdo, int $userId, array $pollIds): array
{
    if (!$pollIds) return [];
    $placeholders = implode(',', array_fill(0, count($pollIds), '?'));
    $params = array_merge([$userId], $pollIds);
    $stmt = $pdo->prepare("SELECT pr.id AS response_id, pr.poll_id, pr.comment, pr.submitted_at, pro.option_id
        FROM poll_responses pr
        LEFT JOIN poll_response_options pro ON pro.response_id = pr.id
        WHERE pr.user_id = ? AND pr.poll_id IN ($placeholders)");
    $stmt->execute($params);
    $out = [];
    foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
        $pollId = (int)$row['poll_id'];
        if (!isset($out[$pollId])) {
            $out[$pollId] = [
                'response_id' => (int)$row['response_id'],
                'comment' => $row['comment'],
                'submitted_at' => $row['submitted_at'],
                'option_ids' => [],
            ];
        }
        if ($row['option_id']) $out[$pollId]['option_ids'][] = (int)$row['option_id'];
    }
    return $out;
}

function pyrk_poll_target_allowed(PDO $pdo, array $poll, int $userId): bool
{
    $scope = $poll['target_scope'] ?? 'all_members';
    if ($scope === 'all_members') return true;
    if ($scope === 'walk_registrants') {
        $walkId = (int)($poll['walk_id'] ?? 0);
        if ($walkId <= 0 || !pyrk_poll_walk_is_visible($pdo, $walkId)) return false;
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM walk_registrations WHERE walk_id = ? AND user_id = ? AND registration_status IN ("registered","approved")');
        $stmt->execute([$walkId, $userId]);
        return (int)$stmt->fetchColumn() > 0;
    }
    if ($scope === 'selected_members') {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM poll_target_members WHERE poll_id = ? AND user_id = ?');
        $stmt->execute([(int)($poll['id'] ?? 0), $userId]);
        return (int)$stmt->fetchColumn() > 0;
    }
    if ($scope === 'selected_groups') {
        $stmt = $pdo->prepare('SELECT COUNT(*)
            FROM poll_target_groups ptg
            JOIN member_group_members mgm ON mgm.group_id = ptg.group_id
            JOIN member_groups g ON g.id = ptg.group_id
            WHERE ptg.poll_id = ? AND mgm.user_id = ? AND g.status = "active"');
        $stmt->execute([(int)($poll['id'] ?? 0), $userId]);
        return (int)$stmt->fetchColumn() > 0;
    }
    return false;
}


function pyrk_poll_walk_table_exists(PDO $pdo, string $table): bool
{
    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?');
        $stmt->execute([$table]);
        return (int)$stmt->fetchColumn() > 0;
    } catch (Throwable) {
        return false;
    }
}

function pyrk_poll_walk_column_exists(PDO $pdo, string $table, string $column): bool
{
    static $cache = [];
    $key = $table . '.' . $column;
    if (array_key_exists($key, $cache)) return $cache[$key];

    try {
        $stmt = $pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = ?');
        $stmt->execute([$table, $column]);
        return $cache[$key] = ((int)$stmt->fetchColumn() > 0);
    } catch (Throwable) {
        return $cache[$key] = false;
    }
}

function pyrk_poll_visible_walk_sql(PDO $pdo, string $alias = 'w'): string
{
    $a = preg_replace('/[^a-zA-Z0-9_]/', '', $alias) ?: 'w';

    $blockedStatuses = [
        'deleted', 'archived', 'removed', 'remove', 'trash', 'trashed',
        'cancelled', 'canceled', 'inactive', 'hidden'
    ];
    $quoted = implode(',', array_map(static fn($s) => '"' . $s . '"', $blockedStatuses));

    $parts = [
        'LOWER(TRIM(COALESCE(' . $a . '.status, ""))) NOT IN (' . $quoted . ')',
    ];

    foreach (['deleted_at', 'removed_at', 'archived_at', 'cancelled_at', 'canceled_at'] as $col) {
        if (pyrk_poll_walk_column_exists($pdo, 'walks', $col)) {
            $parts[] = $a . '.' . $col . ' IS NULL';
        }
    }

    foreach (['is_deleted', 'deleted', 'is_archived', 'archived', 'is_removed'] as $col) {
        if (pyrk_poll_walk_column_exists($pdo, 'walks', $col)) {
            $parts[] = 'COALESCE(' . $a . '.' . $col . ', 0) = 0';
        }
    }

    foreach (['deleted_by', 'removed_by', 'archived_by'] as $col) {
        if (pyrk_poll_walk_column_exists($pdo, 'walks', $col)) {
            $parts[] = $a . '.' . $col . ' IS NULL';
        }
    }

    if (pyrk_poll_walk_table_exists($pdo, 'audit_logs')) {
        $parts[] = 'NOT EXISTS (
            SELECT 1 FROM audit_logs al
            WHERE al.entity_type = "walks"
              AND al.entity_id = ' . $a . '.id
              AND al.action IN ("walk_deleted", "walk_removed", "walk_archived", "walk_cancelled", "walk_canceled", "walk_hidden")
        )';
    }


    // Polls should not offer old deleted/seed/past walks as related walk options.
    // In the uploaded DB, Sunrise Waterfront Walk and Community Park Walk still have status=open,
    // so normal deleted/audit filters alone cannot hide them.
    if (pyrk_poll_walk_column_exists($pdo, 'walks', 'walk_date')) {
        $parts[] = $a . '.walk_date >= CURDATE()';
    }

    $legacySeed = [];
    if (pyrk_poll_walk_column_exists($pdo, 'walks', 'checkin_token')) {
        $legacySeed[] = 'LOWER(COALESCE(' . $a . '.checkin_token, "")) LIKE "pyrk-walk-sunrise-%"';
        $legacySeed[] = 'LOWER(COALESCE(' . $a . '.checkin_token, "")) LIKE "pyrk-walk-park-%"';
    }
    $legacySeed[] = 'LOWER(COALESCE(' . $a . '.title, "")) LIKE "%sunrise waterfront%"';
    $legacySeed[] = 'LOWER(COALESCE(' . $a . '.title, "")) LIKE "%community park%"';
    $parts[] = 'NOT (' . implode(' OR ', $legacySeed) . ')';
    $parts[] = pyrk_walk_access_scope_sql($pdo, $a, Auth::user());

    return '(' . implode(' AND ', $parts) . ')';
}

function pyrk_poll_walk_is_visible(PDO $pdo, int $walkId): bool
{
    if ($walkId <= 0) return false;
    $sql = 'SELECT COUNT(*) FROM walks w WHERE w.id = ? AND ' . pyrk_poll_visible_walk_sql($pdo, 'w');
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$walkId]);
    return (int)$stmt->fetchColumn() > 0;
}



function pyrk_poll_existing_walk_access_guard(PDO $pdo, int $pollId, array $user): void
{
    if ($pollId <= 0) return;
    $stmt = $pdo->prepare('SELECT walk_id FROM polls WHERE id = ? LIMIT 1');
    $stmt->execute([$pollId]);
    $walkId = (int)($stmt->fetchColumn() ?: 0);
    if ($walkId > 0) {
        pyrk_walk_access_require($pdo, $walkId, $user, 'You are not assigned to this poll walk.');
    }
}

function pyrk_poll_status_window_sql(): string
{
    return 'p.status = "active" AND (p.starts_at IS NULL OR p.starts_at <= NOW()) AND (p.ends_at IS NULL OR p.ends_at >= NOW())';
}

if ($method === 'GET' && $action === 'admin') {
    $user = Auth::requireUser();
    pyrk_polls_require_manager($user);
    $pollVisibleWalkSql = pyrk_poll_visible_walk_sql($pdo, 'w2');
    $polls = $pdo->query('SELECT p.*, w.title AS walk_title, u.full_name AS created_by_name,
        (SELECT COUNT(*) FROM poll_responses pr WHERE pr.poll_id = p.id) AS response_count
        FROM polls p
        LEFT JOIN walks w ON w.id = p.walk_id
        LEFT JOIN users u ON u.id = p.created_by
        WHERE p.walk_id IS NULL OR EXISTS (SELECT 1 FROM walks w2 WHERE w2.id = p.walk_id AND ' . $pollVisibleWalkSql . ')
        ORDER BY p.created_at DESC, p.id DESC')->fetchAll(PDO::FETCH_ASSOC);
    $pollIds = array_map(static fn($p) => (int)$p['id'], $polls);
    $options = pyrk_poll_options($pdo, $pollIds);
    $results = pyrk_poll_result_summary($pdo, $pollIds);
    $targetMembers = pyrk_poll_target_members($pdo, $pollIds);
    $targetGroups = pyrk_poll_target_groups($pdo, $pollIds);
    foreach ($polls as &$poll) {
        $pollId = (int)$poll['id'];
        $poll['options'] = $options[$pollId] ?? [];
        $poll['results'] = $results[$pollId] ?? [];
        $poll['target_members'] = $targetMembers[$pollId] ?? [];
        $poll['target_member_ids'] = array_map('intval', array_column($poll['target_members'], 'user_id'));
        $poll['target_groups'] = $targetGroups[$pollId] ?? [];
        $poll['target_group_ids'] = array_map('intval', array_column($poll['target_groups'], 'group_id'));
    }
    unset($poll);
    $comments = [];
    if ($pollIds) {
        $placeholders = implode(',', array_fill(0, count($pollIds), '?'));
        $stmt = $pdo->prepare("SELECT pr.poll_id, pr.comment, pr.submitted_at,
            CASE WHEN p.is_anonymous = 1 THEN 'Anonymous member' ELSE u.full_name END AS member_name
            FROM poll_responses pr
            JOIN polls p ON p.id = pr.poll_id
            JOIN users u ON u.id = pr.user_id
            WHERE pr.poll_id IN ($placeholders) AND pr.comment IS NOT NULL AND pr.comment <> ''
            ORDER BY pr.submitted_at DESC, pr.id DESC
            LIMIT 300");
        $stmt->execute($pollIds);
        foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
            $comments[(int)$row['poll_id']][] = $row;
        }
    }
    foreach ($polls as &$poll) {
        $poll['comments'] = $comments[(int)$poll['id']] ?? [];
    }
    unset($poll);
    $walksSql = 'SELECT w.id, w.title, w.walk_date, w.start_time, w.status
        FROM walks w
        WHERE ' . pyrk_poll_visible_walk_sql($pdo, 'w') . '
        ORDER BY w.walk_date DESC, w.id DESC
        LIMIT 200';
    $walks = $pdo->query($walksSql)->fetchAll(PDO::FETCH_ASSOC);
    $members = pyrk_poll_member_list($pdo);
    $groups = pyrk_poll_group_list($pdo);
    Response::ok(['polls' => $polls, 'walks' => $walks, 'members' => $members, 'groups' => $groups]);
}


if ($method === 'GET' && $action === 'email_preview') {
    $user = Auth::requireUser();
    pyrk_polls_require_manager($user);

    $pollId = (int)($input['poll_id'] ?? 0);
    if (!$pollId) Response::error('poll_id is required.');

    $poll = pyrk_poll_find($pdo, $pollId);
    if (!$poll) Response::error('Poll not found.', 404);

    $recipients = pyrk_poll_email_recipients($pdo, $poll);
    $lastBatch = pyrk_poll_last_email_batch($pdo, $pollId);

    Response::ok([
        'poll' => $poll,
        'recipient_count' => count($recipients),
        'sample_recipients' => array_slice($recipients, 0, 20),
        'subject' => pyrk_poll_email_subject($poll),
        'message' => 'Please share your opinion. Your feedback helps us plan better walks.',
        'poll_link' => pyrk_poll_public_url($pollId),
        'last_batch' => $lastBatch,
        'already_sent' => $lastBatch !== null && (int)($lastBatch['sent_count'] ?? 0) > 0,
    ]);
}

if ($method === 'POST' && $action === 'send_email') {
    $user = Auth::requireUser();
    pyrk_polls_require_manager($user);
    Csrf::validateFromRequest();

    $pollId = (int)($input['poll_id'] ?? 0);
    if (!$pollId) Response::error('poll_id is required.');

    $poll = pyrk_poll_find($pdo, $pollId);
    if (!$poll) Response::error('Poll not found.', 404);

    $recipients = pyrk_poll_email_recipients($pdo, $poll);
    if (!$recipients) Response::error('No active members with valid email addresses were found for this poll.');

    $lastBatch = pyrk_poll_last_email_batch($pdo, $pollId);
    $resend = !empty($input['resend']);
    if ($lastBatch && (int)($lastBatch['sent_count'] ?? 0) > 0 && !$resend) {
        Response::error('This poll was already sent by email. Tick resend to send it again.', 409, ['last_batch' => $lastBatch]);
    }

    $subject = trim((string)($input['subject'] ?? '')) ?: pyrk_poll_email_subject($poll);
    $message = trim((string)($input['message'] ?? ''));
    $pollLink = pyrk_poll_public_url($pollId);

    $pdo->beginTransaction();
    try {
        $pdo->prepare('UPDATE polls SET status = "active", updated_at = NOW() WHERE id = ? AND status <> "closed"')->execute([$pollId]);
        $batchStmt = $pdo->prepare('INSERT INTO poll_email_batches (poll_id, subject, message, recipient_count, sent_count, failed_count, status, created_by, created_at) VALUES (?, ?, ?, ?, 0, 0, "sending", ?, NOW())');
        $batchStmt->execute([$pollId, $subject, $message, count($recipients), (int)$user['id']]);
        $batchId = (int)$pdo->lastInsertId();
        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not create email batch. Did you run migration 026? ' . $e->getMessage());
    }

    $sent = 0;
    $failed = 0;
    $logged = 0;

    foreach ($recipients as $recipient) {
        $html = pyrk_poll_email_html($poll, $recipient, $subject, $message, $pollLink);
        $result = pyrk_poll_send_email_to_member((int)$recipient['user_id'], (string)$recipient['email'], (string)$recipient['name'], $subject, $html);
        $status = (string)($result['status'] ?? 'failed');
        $error = $result['error'] ?? null;

        if (in_array($status, ['sent','logged'], true)) {
            $sent++;
            if ($status === 'logged') $logged++;
        } else {
            $failed++;
        }

        try {
            $stmt = $pdo->prepare('INSERT INTO poll_email_recipients (batch_id, poll_id, user_id, to_email, to_name, status, error_message, sent_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW())');
            $stmt->execute([$batchId, $pollId, (int)$recipient['user_id'], (string)$recipient['email'], (string)$recipient['name'], $status, $error]);
        } catch (Throwable) {
            // keep sending even if recipient log fails
        }
    }

    $finalStatus = $failed > 0 ? ($sent > 0 ? 'partial' : 'failed') : 'sent';
    $pdo->prepare('UPDATE poll_email_batches SET sent_count = ?, failed_count = ?, status = ?, completed_at = NOW() WHERE id = ?')->execute([$sent, $failed, $finalStatus, $batchId]);

    Security::audit((int)$user['id'], 'poll_email_sent', 'polls', $pollId, ['batch_id' => $batchId, 'sent' => $sent, 'failed' => $failed, 'logged' => $logged]);

    Response::ok([
        'batch_id' => $batchId,
        'recipient_count' => count($recipients),
        'sent_count' => $sent,
        'failed_count' => $failed,
        'logged_count' => $logged,
        'status' => $finalStatus,
    ], 'Poll email finished: ' . $sent . ' sent/logged, ' . $failed . ' failed.');
}

if ($method === 'GET' && $action === 'active') {
    $user = Auth::requireUser();
    $polls = $pdo->query('SELECT p.*, w.title AS walk_title, w.walk_date,
        (SELECT COUNT(*) FROM poll_responses pr WHERE pr.poll_id = p.id) AS response_count
        FROM polls p
        LEFT JOIN walks w ON w.id = p.walk_id
        WHERE ' . pyrk_poll_status_window_sql() . '
          AND (p.walk_id IS NULL OR (' . pyrk_poll_visible_walk_sql($pdo, 'w') . '))
        ORDER BY p.created_at DESC, p.id DESC')->fetchAll(PDO::FETCH_ASSOC);
    $polls = array_values(array_filter($polls, static fn($poll) => pyrk_poll_target_allowed($pdo, $poll, (int)$user['id'])));
    $pollIds = array_map(static fn($p) => (int)$p['id'], $polls);
    $options = pyrk_poll_options($pdo, $pollIds);
    $myResponses = pyrk_poll_my_responses($pdo, (int)$user['id'], $pollIds);
    foreach ($polls as &$poll) {
        $pollId = (int)$poll['id'];
        $poll['options'] = $options[$pollId] ?? [];
        $poll['my_response'] = $myResponses[$pollId] ?? null;
        $poll['has_responded'] = isset($myResponses[$pollId]);
    }
    unset($poll);
    Response::ok(['polls' => $polls]);
}

if ($method === 'POST' && $action === 'save') {
    $user = Auth::requireUser();
    pyrk_polls_require_manager($user);
    Csrf::validateFromRequest();
    $id = (int)($input['id'] ?? 0);
    $title = trim((string)($input['title'] ?? ''));
    if ($title === '') Response::error('Poll title is required.');
    $description = trim((string)($input['description'] ?? ''));
    $pollType = strtolower(trim((string)($input['poll_type'] ?? 'general')));
    if (!in_array($pollType, ['general','timing','date','location','route','other'], true)) $pollType = 'general';
    $targetScope = strtolower(trim((string)($input['target_scope'] ?? 'all_members')));
    if (!in_array($targetScope, ['all_members','walk_registrants','selected_members','selected_groups'], true)) $targetScope = 'all_members';
    $walkId = (int)($input['walk_id'] ?? 0);
    if ($targetScope !== 'walk_registrants') $walkId = 0;
    if ($targetScope === 'walk_registrants' && $walkId <= 0) Response::error('Select a walk for walk registrant polls.');
    if ($targetScope === 'walk_registrants' && !pyrk_poll_walk_is_visible($pdo, $walkId)) {
        Response::error('Selected related walk is deleted, hidden, past or unavailable for polls.');
    }
    if ($targetScope === 'walk_registrants' && $walkId > 0 && !pyrk_poll_walk_is_visible($pdo, $walkId)) {
        Response::error('This walk is deleted, cancelled, or archived and cannot be used for polls.', 404);
    }
    $memberIds = pyrk_poll_member_ids_from_input($input);
    $groupIds = pyrk_poll_group_ids_from_input($input);
    if ($targetScope === 'selected_members' && count($memberIds) === 0) Response::error('Select at least one member.');
    if ($targetScope === 'selected_groups' && count($groupIds) === 0) Response::error('Select at least one team/group.');
    $status = strtolower(trim((string)($input['status'] ?? 'draft')));
    if (!in_array($status, ['draft','active','closed'], true)) $status = 'draft';
    $allowMultiple = !empty($input['allow_multiple']) ? 1 : 0;
    $isAnonymous = !empty($input['is_anonymous']) ? 1 : 0;
    $startsAt = pyrk_poll_date_window($input['starts_at'] ?? null, false);
    $endsAt = pyrk_poll_date_window($input['ends_at'] ?? null, true);
    if ($startsAt && $endsAt && strtotime($startsAt) > strtotime($endsAt)) Response::error('Start date cannot be after end date.');
    $rawOptions = $input['options_text'] ?? '';
    if (is_array($rawOptions)) $rawOptions = implode("\n", $rawOptions);
    $optionLines = array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', (string)$rawOptions)), static fn($x) => $x !== ''));
    $optionLines = array_values(array_unique($optionLines));
    if (count($optionLines) < 2) Response::error('Add at least two poll options.');
    $pdo->beginTransaction();
    try {
        if ($id > 0) {
            $beforeStmt = $pdo->prepare('SELECT * FROM polls WHERE id = ? LIMIT 1');
            $beforeStmt->execute([$id]);
            $before = $beforeStmt->fetch(PDO::FETCH_ASSOC);
            if (!$before) Response::error('Poll not found.', 404);
            $stmt = $pdo->prepare('UPDATE polls SET title=?, description=?, poll_type=?, target_scope=?, walk_id=?, allow_multiple=?, is_anonymous=?, status=?, starts_at=?, ends_at=?, updated_at=NOW() WHERE id=?');
            $stmt->execute([$title, $description, $pollType, $targetScope, $walkId ?: null, $allowMultiple, $isAnonymous, $status, $startsAt, $endsAt, $id]);
            $pdo->prepare('DELETE FROM poll_options WHERE poll_id = ?')->execute([$id]);
            $pollId = $id;
            Security::audit((int)$user['id'], 'poll_updated', 'polls', $pollId, ['title' => $title, 'status' => $status], $before);
        } else {
            $stmt = $pdo->prepare('INSERT INTO polls (title, description, poll_type, target_scope, walk_id, allow_multiple, is_anonymous, status, starts_at, ends_at, created_by, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())');
            $stmt->execute([$title, $description, $pollType, $targetScope, $walkId ?: null, $allowMultiple, $isAnonymous, $status, $startsAt, $endsAt, (int)$user['id']]);
            $pollId = (int)$pdo->lastInsertId();
            Security::audit((int)$user['id'], 'poll_created', 'polls', $pollId, ['title' => $title, 'status' => $status]);
        }
        $optStmt = $pdo->prepare('INSERT INTO poll_options (poll_id, option_text, sort_order, created_at) VALUES (?, ?, ?, NOW())');
        foreach ($optionLines as $idx => $option) $optStmt->execute([$pollId, $option, $idx + 1]);
        $pdo->prepare('DELETE FROM poll_target_members WHERE poll_id = ?')->execute([$pollId]);
        $pdo->prepare('DELETE FROM poll_target_groups WHERE poll_id = ?')->execute([$pollId]);
        if ($targetScope === 'selected_members') {
            $targetStmt = $pdo->prepare('INSERT INTO poll_target_members (poll_id, user_id, created_at) VALUES (?, ?, NOW())');
            foreach ($memberIds as $memberId) $targetStmt->execute([$pollId, $memberId]);
        }
        if ($targetScope === 'selected_groups') {
            $groupStmt = $pdo->prepare('INSERT INTO poll_target_groups (poll_id, group_id, created_at) VALUES (?, ?, NOW())');
            foreach ($groupIds as $groupId) $groupStmt->execute([$pollId, $groupId]);
        }
        $pdo->commit();
        Response::ok(['poll_id' => $pollId], $id > 0 ? 'Poll updated.' : 'Poll created.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not save poll: ' . $e->getMessage());
    }
}

if ($method === 'POST' && $action === 'set_status') {
    $user = Auth::requireUser();
    pyrk_polls_require_manager($user);
    Csrf::validateFromRequest();
    $id = (int)($input['id'] ?? 0);
    $status = strtolower(trim((string)($input['status'] ?? '')));
    if (!$id || !in_array($status, ['draft','active','closed'], true)) Response::error('Invalid poll status request.');
    pyrk_poll_existing_walk_access_guard($pdo, $id, $user);
    $pdo->prepare('UPDATE polls SET status = ?, updated_at = NOW() WHERE id = ?')->execute([$status, $id]);
    Security::audit((int)$user['id'], 'poll_status_changed', 'polls', $id, ['status' => $status]);
    Response::ok([], 'Poll status updated.');
}

if ($method === 'POST' && $action === 'submit') {
    $user = Auth::requireUser();
    Csrf::validateFromRequest();
    $pollId = (int)($input['poll_id'] ?? 0);
    if (!$pollId) Response::error('poll_id is required.');
    $stmt = $pdo->prepare('SELECT * FROM polls p WHERE p.id = ? AND ' . pyrk_poll_status_window_sql() . ' LIMIT 1');
    $stmt->execute([$pollId]);
    $poll = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$poll) Response::error('Poll is closed or not available.', 404);
    if (!pyrk_poll_target_allowed($pdo, $poll, (int)$user['id'])) Response::error('This poll is not assigned to you.', 403);
    $optionIds = $input['option_ids'] ?? $input['option_id'] ?? [];
    if (!is_array($optionIds)) $optionIds = [$optionIds];
    $optionIds = array_values(array_unique(array_filter(array_map('intval', $optionIds))));
    if (!$optionIds) Response::error('Select an option.');
    if (empty($poll['allow_multiple']) && count($optionIds) > 1) Response::error('This poll allows one option only.');
    $placeholders = implode(',', array_fill(0, count($optionIds), '?'));
    $params = array_merge([$pollId], $optionIds);
    $validStmt = $pdo->prepare("SELECT id FROM poll_options WHERE poll_id = ? AND id IN ($placeholders)");
    $validStmt->execute($params);
    $valid = array_map('intval', array_column($validStmt->fetchAll(PDO::FETCH_ASSOC), 'id'));
    if (count($valid) !== count($optionIds)) Response::error('Invalid poll option selected.');
    $existing = $pdo->prepare('SELECT id FROM poll_responses WHERE poll_id = ? AND user_id = ? LIMIT 1');
    $existing->execute([$pollId, (int)$user['id']]);
    if ($existing->fetchColumn()) Response::error('You already submitted your opinion for this poll.', 409);
    $comment = trim((string)($input['comment'] ?? ''));
    $pdo->beginTransaction();
    try {
        $resp = $pdo->prepare('INSERT INTO poll_responses (poll_id, user_id, comment, submitted_at, created_at) VALUES (?, ?, ?, NOW(), NOW())');
        $resp->execute([$pollId, (int)$user['id'], $comment]);
        $responseId = (int)$pdo->lastInsertId();
        $link = $pdo->prepare('INSERT INTO poll_response_options (response_id, option_id) VALUES (?, ?)');
        foreach ($optionIds as $optionId) $link->execute([$responseId, $optionId]);
        $pdo->commit();
        Response::ok(['response_id' => $responseId], 'Thank you. Your opinion was submitted.');
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        Response::error('Could not submit opinion: ' . $e->getMessage());
    }
}

Response::error('Unsupported polls action.', 404);
