<?php
declare(strict_types=1);

// ==================================================================
// 1. CONFIGURATION & CORE INITIALIZATION
// ==================================================================

class Config
{
    private static ?PDO $pdo = null;

    public static function init(): void
    {
        self::loadEnv(__DIR__ . '/.env');
        self::handleCors();
        self::setSecurityHeaders();
        self::startSecureSession();
    }

    private static function loadEnv(string $filePath): bool
    {
        if (!file_exists($filePath) || !is_readable($filePath)) {
            return false;
        }
        $lines = file($filePath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        foreach ($lines as $line) {
            $line = trim($line);
            if ($line === '' || str_starts_with($line, '#')) {
                continue;
            }
            if (strpos($line, '=') !== false) {
                list($name, $value) = explode('=', $line, 2);
                $name = trim($name);
                $value = trim(trim($value), "\"'");
                if (!array_key_exists($name, $_SERVER) && !array_key_exists($name, $_ENV)) {
                    putenv(sprintf('%s=%s', $name, $value));
                    $_ENV[$name] = $value;
                    $_SERVER[$name] = $value;
                }
            }
        }
        return true;
    }

    private static function handleCors(): void
    {
        $allowedOrigin = getenv('CORS_ALLOWED_ORIGIN');
        $origin = $_SERVER['HTTP_ORIGIN'] ?? '';

        if (!empty($origin) && !empty($allowedOrigin) && $origin === $allowedOrigin) {
            header("Access-Control-Allow-Origin: $origin");
            header('Access-Control-Allow-Credentials: true');
            header('Vary: Origin');
        }

        if (($_SERVER['REQUEST_METHOD'] ?? '') === 'OPTIONS') {
            header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
            header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
            http_response_code(204);
            exit;
        }
    }

    private static function setSecurityHeaders(): void
    {
        header('Content-Type: application/json; charset=utf-8');
        header('X-Content-Type-Options: nosniff');
        header('X-Frame-Options: DENY');
        header('X-XSS-Protection: 1; mode=block');
        header('Referrer-Policy: strict-origin-when-cross-origin');
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With');
    }
    
    private static function startSecureSession(): void
    {
        if (session_status() === PHP_SESSION_NONE) {
            $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['SERVER_PORT'] ?? 0) == 443);
            session_start([
                'cookie_httponly'  => true,
                'cookie_secure'    => (bool)(getenv('SESSION_COOKIE_SECURE') ?? $isHttps),
                'use_only_cookies' => true,
                'use_strict_mode'  => true,
                'cookie_samesite'  => 'Lax'
            ]);
        }
    }

    public static function getDatabaseConnection(): PDO
    {
        if (self::$pdo !== null) {
            return self::$pdo;
        }

        $host = getenv('DB_HOST');
        $db   = getenv('DB_NAME');
        $user = getenv('DB_USER');
        $pass = getenv('DB_PASS');

        if (!$host || !$db || !$user) {
            error_log('Database configuration missing in .env file');
            http_response_code(500);
            echo json_encode(['success' => false, 'message' => 'Server Configuration Error']);
            exit;
        }

        try {
            self::$pdo = new PDO("mysql:host={$host};dbname={$db};charset=utf8mb4", $user, $pass, [
                PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                PDO::ATTR_EMULATE_PREPARES   => false,
            ]);
            return self::$pdo;
        } catch (PDOException $e) {
            error_log('Database Connection Error: ' . $e->getMessage());
            http_response_code(500);
            echo json_encode(['success' => false, 'message' => 'Database connection failed']);
            exit;
        }
    }
}

// ==================================================================
// 2. ENUMS & RESTORE TYPE RESOLUTION
// ==================================================================

enum RestoreType: string
{
    case EMAIL = 'email_restore_requests';
    case UTR   = 'utr_restore_requests';

    public static function tryFromInput(?string $type): ?self
    {
        return match ($type) {
            'email' => self::EMAIL,
            'utr'   => self::UTR,
            default => null,
        };
    }

    public function getTargetTable(): string
    {
        return match ($this) {
            self::EMAIL => 'email_restore_requests',
            self::UTR   => 'utr_restore_requests',
        };
    }
}

// ==================================================================
// 3. API CONTROLLER BUSINESS LOGIC
// ==================================================================

class ApiController
{
    private PDO $pdo;
    private array $inputData;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
        $this->inputData = $this->getJsonInput();
    }

    private function jsonResponse(array $data, int $statusCode = 200): void
    {
        http_response_code($statusCode);
        echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        exit;
    }

    private function sendJsonResponse(bool $success, string $message, array $extraData = [], int $statusCode = 200): void
    {
        $this->jsonResponse(array_merge([
            'success' => $success,
            'message' => $message
        ], $extraData), $statusCode);
    }

    private function requireAuth(): string
    {
        if (empty($_SESSION['user']['userId'])) {
            $this->jsonResponse([
                'success' => false,
                'isLoggedIn' => false,
                'message' => 'Unauthorized access. Pehle login karein.'
            ], 401);
        }
        return (string)$_SESSION['user']['userId'];
    }

    private function checkAdminAccess(): void
    {
        $this->requireAuth();
        if (($_SESSION['user']['role'] ?? '') !== 'admin') {
            $this->sendJsonResponse(false, 'Access denied. Admin privileges required.', [], 403);
        }
    }

    private function getJsonInput(): array
    {
        $rawInput = file_get_contents('php://input');
        if (empty($rawInput)) {
            return [];
        }
        $decoded = json_decode($rawInput, true);
        return is_array($decoded) ? $decoded : [];
    }

    private function getClientIP(): string
    {
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }

    public function getInputData(): array
    {
        return $this->inputData;
    }

    public function register(): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->sendJsonResponse(false, 'Method Not Allowed', [], 405);
        }

        $userId   = trim($this->inputData['userId'] ?? '');
        $username = trim($this->inputData['username'] ?? '');
        $email    = strtolower(trim($this->inputData['email'] ?? ''));
        $password = $this->inputData['password'] ?? '';

        if ($userId === '' || $username === '' || $email === '' || $password === '') {
            $this->sendJsonResponse(false, 'Tamam fields bharna zaroori hai!', [], 400);
        }

        if (!filter_var($email, FILTER_VALIDATE_EMAIL) || !str_ends_with($email, '@gmail.com')) {
            $this->sendJsonResponse(false, 'Keval valid @gmail.com email hi allowed hai!', [], 400);
        }

        $stmtCheck = $this->pdo->prepare("SELECT user_id, email FROM users WHERE user_id = ? OR email = ?");
        $stmtCheck->execute([$userId, $email]);
        $existingUsers = $stmtCheck->fetchAll();

        foreach ($existingUsers as $existing) {
            if ($existing['email'] === $email) {
                $this->sendJsonResponse(false, 'Ye email pehle se registered hai!', [], 400);
            }

            if ($existing['user_id'] === $userId) {
                $candidates = array_map(fn() => $userId . rand(100, 999), range(1, 6));
                $candidates = array_values(array_unique($candidates));

                $inClause = implode(',', array_fill(0, count($candidates), '?'));
                $chkStmt = $this->pdo->prepare("SELECT user_id FROM users WHERE user_id IN ($inClause)");
                $chkStmt->execute($candidates);
                $taken = $chkStmt->fetchAll(PDO::FETCH_COLUMN);

                $suggestions = array_slice(array_values(array_diff($candidates, $taken)), 0, 3);
                $this->sendJsonResponse(false, 'User ID pehle se li ja chuki hai.', ['suggestions' => $suggestions], 400);
            }
        }

        $algo = defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT;
        $hashedPassword = password_hash($password, $algo);

        $insertStmt = $this->pdo->prepare("INSERT INTO users (user_id, username, email, password) VALUES (?, ?, ?, ?)");
        if ($insertStmt->execute([$userId, $username, $email, $hashedPassword])) {
            $this->sendJsonResponse(true, 'Registration successful!', [], 201);
        }
        $this->sendJsonResponse(false, 'Registration fail ho gaya, kripya dobara koshish karein.', [], 500);
    }

    public function login(): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
            $this->sendJsonResponse(false, 'Method Not Allowed', [], 405);
        }
        $identifier = trim($this->inputData['identifier'] ?? '');
        $password = $this->inputData['password'] ?? '';


if ($identifier === '' || $password === '') {
$this->sendJsonResponse(false, 'Galat User ID/Email ya Password!', [], 400);
}
$clientIP = $this->getClientIP();
$maxAttempts = 5;
$timeThreshold = time() - (15 * 60);
$rateStmt = $this->pdo->prepare("SELECT COUNT(*) FROM login_attempts WHERE (ip_address = ? OR identifier = ?) AND attempt_time > ?");
$rateStmt->execute([$clientIP, strtolower($identifier), $timeThreshold]);
if ((int)$rateStmt->fetchColumn() >= $maxAttempts) {
$this->sendJsonResponse(false, 'Bahut saare galat attempts! Login 15 minute ke liye block hai.', [], 429);
}
$stmt = $this->pdo->prepare("SELECT user_id, username, email, password, about, role FROM users WHERE user_id = ? OR email = ?");
$stmt->execute([$identifier, $identifier]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$clearStmt = $this->pdo->prepare("DELETE FROM login_attempts WHERE ip_address = ? OR identifier = ?");
$clearStmt->execute([$clientIP, strtolower($identifier)]);
session_regenerate_id(true);
$_SESSION['user'] = [
'userId' => $user['user_id'],
'username' => $user['username'],
'email' => $user['email'],
'about' => $user['about'] ?? '',
'role' => $user['role'] ?? 'user'
];
$this->jsonResponse(['success' => true, 'user' => $_SESSION['user']]);
}
$logStmt = $this->pdo->prepare("INSERT INTO login_attempts (ip_address, identifier, attempt_time) VALUES (?, ?, ?)");
$logStmt->execute([$clientIP, strtolower($identifier), time()]);
$this->sendJsonResponse(false, 'Galat User ID/Email ya Password!', [], 401);
}
public function logout(): void
{
$_SESSION = [];
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
}
session_destroy();
$this->sendJsonResponse(true, 'Logout successful.');
}
public function getUserData(): void
{
$userId = $this->requireAuth();
try {
$stmt = $this->pdo->prepare("SELECT exam_name FROM user_preferences WHERE user_id = ?");
$stmt->execute([$userId]);
$prefs = $stmt->fetchAll(PDO::FETCH_COLUMN);
$stmt = $this->pdo->prepare("
SELECT id, upheading, topic, subtopic,
test_name AS testName,
marks_scored AS marksScored,
total_marks AS totalMarks,
correct, incorrect, unattempted,
total_questions AS totalQuestions,
DATE_FORMAT(created_at, '%d/%m/%Y') AS date
FROM test_reports
WHERE user_id = ?
ORDER BY id DESC
");
$stmt->execute([$userId]);
$reports = $stmt->fetchAll(PDO::FETCH_ASSOC);
$this->jsonResponse([
'success' => true,
'isLoggedIn' => true,
'user' => $_SESSION['user'],
'preferences' => $prefs,
'reports' => $reports
]);
} catch (PDOException $e) {
error_log("Database Error [get_user_data]: " . $e->getMessage());
$this->sendJsonResponse(false, 'Internal server error.', [], 500);
}
}
public function savePreferences(): void
{
$userId = $this->requireAuth();
$rawPrefs = is_array($this->inputData['preferences'] ?? null) ? $this->inputData['preferences'] : [];
$prefs = array_slice($rawPrefs, 0, 50);
try {
$this->pdo->beginTransaction();
$stmt = $this->pdo->prepare("DELETE FROM user_preferences WHERE user_id = ?");
$stmt->execute([$userId]);
if (!empty($prefs)) {
$insertValues = [];
$queryParams = [];
foreach ($prefs as $pref) {
$cleanPref = trim((string)$pref);
if ($cleanPref !== '' && strlen($cleanPref) <= 100) {
$insertValues[] = "(?, ?)";
$queryParams[] = $userId;
$queryParams[] = $cleanPref;
}
}
if (!empty($insertValues)) {
$sql = "INSERT INTO user_preferences (user_id, exam_name) VALUES " . implode(', ', $insertValues);
$stmt = $this->pdo->prepare($sql);
$stmt->execute($queryParams);
}
}
$this->pdo->commit();
$this->sendJsonResponse(true, 'Preferences saved successfully.');
} catch (PDOException $e) {
if ($this->pdo->inTransaction()) {
$this->pdo->rollBack();
}
error_log("Database Error [save_preferences]: " . $e->getMessage());
$this->sendJsonResponse(false, 'Failed to save preferences.', [], 500);
}
}
public function saveReport(): void
{
$userId = $this->requireAuth();
try {
$stmt = $this->pdo->prepare("
INSERT INTO test_reports
(user_id, upheading, topic, subtopic, test_name, marks_scored, total_marks, correct, incorrect, unattempted, total_questions)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$userId,
mb_strimwidth(trim((string)($this->inputData['upheading'] ?? '')), 0, 150),
mb_strimwidth(trim((string)($this->inputData['topic'] ?? '')), 0, 150),
mb_strimwidth(trim((string)($this->inputData['subtopic'] ?? '')), 0, 150),
mb_strimwidth(trim((string)($this->inputData['testName'] ?? '')), 0, 150),
(float)($this->inputData['marksScored'] ?? 0),
(float)($this->inputData['totalMarks'] ?? 0),
(int)($this->inputData['correct'] ?? 0),
(int)($this->inputData['incorrect'] ?? 0),
(int)($this->inputData['unattempted'] ?? 0),
(int)($this->inputData['totalQuestions'] ?? 0)
]);
$this->sendJsonResponse(true, 'Report saved successfully.');
} catch (PDOException $e) {
error_log("Database Error [save_report]: " . $e->getMessage());
$this->sendJsonResponse(false, 'Failed to save test report.', [], 500);
}
}
public function getQuestions(): void
{
try {
$stmt = $this->pdo->prepare("
SELECT id, UPheading, topic, subtopic, testName, question, options, correct
FROM questions ORDER BY id DESC
");
$stmt->execute();
$questions = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($questions as &$q) {
if (!empty($q['options']) && is_string($q['options'])) {
$decoded = json_decode($q['options'], true);
$q['options'] = (json_last_error() === JSON_ERROR_NONE) ? $decoded : $q['options'];
}
}
unset($q);
$this->jsonResponse(['status' => 'success', 'data' => $questions]);
} catch (PDOException $e) {
error_log("Database Error [get_questions]: " . $e->getMessage());
$this->jsonResponse(['status' => 'error', 'message' => 'Internal Server Error'], 500);
}
}
public function clearAttemptHistory(): void
{
$userId = $this->requireAuth();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->sendJsonResponse(false, 'Method Not Allowed', [], 405);
}
try {
$stmt = $this->pdo->prepare("DELETE FROM test_reports WHERE user_id = ?");
$stmt->execute([$userId]);
$affected = $stmt->rowCount();
$this->sendJsonResponse(true, 'Attempt history successfully clear ho gayi!', [
'deleted_rows' => $affected
]);
} catch (PDOException $e) {
error_log("Clear History Error: " . $e->getMessage());
$this->sendJsonResponse(false, 'History clear karne me error aaya.', [], 500);
}
}
public function clearReports(): void
{
$userId = $this->requireAuth();
try {
$stmt = $this->pdo->prepare("DELETE FROM test_reports WHERE user_id = ?");
$stmt->execute([$userId]);
$this->sendJsonResponse(true, 'Reports cleared successfully.');
} catch (PDOException $e) {
error_log("Database Error [clear_reports]: " . $e->getMessage());
$this->sendJsonResponse(false, 'Failed to clear reports.', [], 500);
}
}
public function updateProfile(): void
{
$userId = $this->requireAuth();
$username = trim((string)($this->inputData['username'] ?? ''));
$about = trim((string)($this->inputData['about'] ?? ''));
if ($username === '' || mb_strlen($username) > 50) {
$this->sendJsonResponse(false, 'Username must be between 1 and 50 characters.', [], 400);
}
try {
$stmt = $this->pdo->prepare("UPDATE users SET username = ?, about = ? WHERE user_id = ?");
$stmt->execute([$username, $about, $userId]);
$_SESSION['user']['username'] = $username;
$_SESSION['user']['about'] = $about;
$this->jsonResponse(['success' => true, 'user' => $_SESSION['user']]);
} catch (PDOException $e) {
error_log("Database Error [update_profile]: " . $e->getMessage());
$this->sendJsonResponse(false, 'Failed to update profile.', [], 500);
}
}
public function changePassword(): void
{
$userId = $this->requireAuth();
$currentPassword = $this->inputData['currentPassword'] ?? '';
$newPassword = $this->inputData['newPassword'] ?? '';
if (empty($currentPassword) || empty($newPassword)) {
$this->sendJsonResponse(false, 'Donon password fields bharna zaroori hai!', [], 400);
}
if (strlen($newPassword) < 8) {
$this->sendJsonResponse(false, 'Naya password kam se kam 8 characters ka hona chahiye!', [], 400);
}
try {
$stmt = $this->pdo->prepare("SELECT password FROM users WHERE user_id = ?");
$stmt->execute([$userId]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($currentPassword, $user['password'])) {
$algo = defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT;
$hashedPassword = password_hash($newPassword, $algo);
$updateStmt = $this->pdo->prepare("UPDATE users SET password = ? WHERE user_id = ?");
$updateStmt->execute([$hashedPassword, $userId]);
$this->sendJsonResponse(true, 'Password safaltapurvak badal diya gaya hai!');
} else {
$this->sendJsonResponse(false, 'Aapka purana password galat hai!', [], 400);
}
} catch (PDOException $e) {
error_log("Password Change Error: " . $e->getMessage());
$this->sendJsonResponse(false, 'Kuch galat hua. Kripya baad mein prayas karein.', [], 500);
}
}
public function submitPayment(): void
{
$userId = $this->requireAuth();
$email = $_SESSION['user']['email'] ?? '';
$utr = trim($_POST['utr'] ?? '');
$months = filter_var($_POST['months'] ?? 1, FILTER_VALIDATE_INT, ["options" => ["min_range" => 1]]);
$amount = filter_var($_POST['amount'] ?? 0, FILTER_VALIDATE_FLOAT);
if (empty($utr) || !$months || !$amount || $amount <= 0 || !isset($_FILES['proof_image']) || $_FILES['proof_image']['error'] !== UPLOAD_ERR_OK) {
$this->sendJsonResponse(false, 'Details aur proof upload karein.', [], 400);
}
$fileTmpPath = $_FILES['proof_image']['tmp_name'];
if ($_FILES['proof_image']['size'] > 5 * 1024 * 1024) {
$this->sendJsonResponse(false, 'File ka size 5MB se kam hona chahiye.', [], 400);
}
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $fileTmpPath);
finfo_close($finfo);
$allowedMimeTypes = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'];
if (!array_key_exists($mimeType, $allowedMimeTypes)) {
$this->sendJsonResponse(false, 'Keval Image files allowed hain.', [], 400);
}
$uploadDir = __DIR__. '/uploads/payments/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$newFileName = time() . '' . rand(1000, 9999) . '.' . $allowedMimeTypes[$mimeType];
$targetFile = $uploadDir . $newFileName;
if (move_uploaded_file($fileTmpPath, $targetFile)) {
try {
$stmt = $this->pdo->prepare("INSERT INTO payments (user_id, email, utr, months, amount, proof_image, status) VALUES (?, ?, ?, ?, ?, ?, 'PENDING')");
$stmt->execute([$userId, $email, $utr, $months, $amount, 'uploads/payments/' . $newFileName]);
$this->sendJsonResponse(true, 'Payment submit ho gaya! Admin approval ka wait karein.');
} catch (PDOException $e) {
if (file_exists($targetFile)) {
unlink($targetFile);
}
error_log("Payment Submit DB Error: " . $e->getMessage());
$this->sendJsonResponse(false, 'Database Error.', [], 500);
}
} else {
$this->sendJsonResponse(false, 'File save karne me error aaya.', [], 500);
}
}


public function getPaymentStatus(): void
{
    if (empty($_SESSION['user']['userId'])) {
        $this->jsonResponse(['success' => false, 'premiumStatus' => 'INACTIVE', 'isPremium' => false, 'expiryDate' => null]);
    }

    $userId = $_SESSION['user']['userId'];

    try {
        // [PERFECT FIX]: हमेशा वह APPROVED प्लान उठाओ जिसकी Expiry Date सबसे दूर (Maximum) है और जो अभी एक्सपायर नहीं हुआ है
        $stmt = $this->pdo->prepare("
            SELECT status, expiry_date 
            FROM payments 
            WHERE user_id = ? AND status = 'APPROVED' AND expiry_date > NOW() 
            ORDER BY expiry_date DESC 
            LIMIT 1
        ");
        $stmt->execute([$userId]);
        $activePayment = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($activePayment) {
            $expiry = $activePayment['expiry_date'];
            $this->jsonResponse([
                'success' => true,
                'premiumStatus' => 'ACTIVE',
                'isPremium' => true,
                'expiryDate' => date("d M Y, h:i A", strtotime($expiry))
            ]);
            return; // कोड को यहीं रोक दें क्योंकि एक्टिव प्लान मिल चुका है
        }
    } catch (PDOException $e) {
        error_log("Payment Status DB Error: " . $e->getMessage());
    }

    // अगर कोई भीै वैलिड एक्टिव प्लान नहीं मिलता है, तभी यूजर को INACTIVE दिखाओ
    $this->jsonResponse([
        'success' => true,
        'premiumStatus' => 'INACTIVE',
        'isPremium' => false,
        'expiryDate' => null
    ]);
}


public function verifyRazorpayPayment(): void
{
    $userId = $this->requireAuth();
    $paymentId = trim($this->inputData['razorpay_payment_id'] ?? '');
    
    // Hamare JavaScript se bheje gaye dynamic parameters
    $months = filter_var($this->inputData['months'] ?? 1, FILTER_VALIDATE_INT, ["options" => ["min_range" => 1]]);
    $amount = filter_var($this->inputData['amount'] ?? 0, FILTER_VALIDATE_FLOAT);
    $email = $_SESSION['user']['email'] ?? '';

    // Agr paymentId khali hai to error return karein
    if (empty($paymentId) || !$months || !$amount || $amount <= 0) {
        $this->sendJsonResponse(false, 'Invalid Payment Details!', [], 400);
    }

    try {
        // 1. Purana active plan dhoondein taaki bache hue din naye plan mein jud sakein
        $checkStmt = $this->pdo->prepare("
            SELECT expiry_date 
            FROM payments 
            WHERE user_id = ? AND status = 'APPROVED' AND expiry_date > NOW() 
            ORDER BY expiry_date DESC 
            LIMIT 1
        ");
        $checkStmt->execute([$userId]);
        $activePayment = $checkStmt->fetch(PDO::FETCH_ASSOC);

        if ($activePayment && !empty($activePayment['expiry_date'])) {
            $baseDate = $activePayment['expiry_date'];
        } else {
            $baseDate = date('Y-m-d H:i:s');
        }

        // 2. Nayi Expiry Date nikaalein
        $expiryDate = date('Y-m-d H:i:s', strtotime("+$months months", strtotime($baseDate)));

        // 3. Database entry karein (Status ko direct APPROVED set karein kyunki Razorpay handler chal chuka hai)
        $stmt = $this->pdo->prepare("
            INSERT INTO payments (user_id, email, razorpay_payment_id, months, amount, status, expiry_date) 
            VALUES (?, ?, ?, ?, ?, 'APPROVED', ?)
        ");
        $stmt->execute([$userId, $email, $paymentId, $months, $amount, $expiryDate]);

        // JSON Response bhejien taaki browser screen par confirmation popup dikhe
        $this->sendJsonResponse(true, 'Payment successful aur status APPROVED ho gaya!', [
            'expiryDate' => date("d M Y, h:i A", strtotime($expiryDate))
        ]);

    } catch (PDOException $e) {
        error_log("Razorpay Payment DB Error: " . $e->getMessage());
        $this->sendJsonResponse(false, 'Database Error! Data save nahi ho paya.', [], 500);
    }
}




public function getQrImage(): void
{
try {
$stmt = $this->pdo->query("SELECT qr_image FROM admin_settings ORDER BY id DESC LIMIT 1");
$qr = $stmt->fetchColumn();
if ($qr) {
$this->jsonResponse([
'status' => 'success',
'qr_url' => 'admin/uploads/' . basename($qr)
]);
}
$uploadDir = 'admin/uploads/';
$files = glob($uploadDir . '*.{jpg,jpeg,png,webp}', GLOB_BRACE);
if (!empty($files)) {
usort($files, function($a, $b) {
return filemtime($b) - filemtime($a);
});
$this->jsonResponse([
'status' => 'success',
'qr_url' => $files[0]
]);
} else {
$this->jsonResponse([
'status' => 'error',
'qr_url' => 'images/my_qr_code.png',
'message' => 'No QR found in admin uploads'
]);
}
} catch (PDOException $e) {
error_log("QR Fetch Error: " . $e->getMessage());
$this->jsonResponse(['status' => 'error', 'qr_url' => 'images/my_qr_code.png', 'message' => 'Internal server error'], 500);
}
}
public function forgotPassword(): void
{
$identity = trim($this->inputData['identity'] ?? $_POST['identity'] ?? '');
if (empty($identity)) {
$this->sendJsonResponse(false, 'Kripya User ID ya Email darj karein!', ['status' => 'error'], 400);
}
try {
$userStmt = $this->pdo->prepare("SELECT id, user_id, email FROM users WHERE user_id = ? OR email = ? LIMIT 1");
$userStmt->execute([$identity, $identity]);
$user = $userStmt->fetch();
if ($user) {
$email = $user['email'];
$token = bin2hex(random_bytes(32));
$tokenStmt = $this->pdo->prepare("INSERT INTO password_resets (email, token) VALUES (?, ?)");
$tokenStmt->execute([$email, $token]);
$domain = getenv('CORS_ALLOWED_ORIGIN') ?: "https://tallyrashan.xyz";
$encoded_email = urlencode($email);
$resetLink = $domain . "/reset-password.php?token=" . $token . "&email=" . $encoded_email;
$reqStmt = $this->pdo->prepare("INSERT INTO email_restore_requests (email, user_id, status) VALUES (?, ?, 'PENDING')");
$reqStmt->execute([$user['email'], $user['user_id']]);
$this->sendJsonResponse(true, 'Password reset link safaltapurvak generate ho gaya!', [
'status' => 'success',
'reset_link' => $resetLink
]);
} else {
$this->sendJsonResponse(false, 'Yeh User ID ya Email registered nahi hai. Kripya sahi detail dalein!', ['status' => 'error'], 404);
}
} catch (PDOException $e) {
error_log("Forgot Password Error: " . $e->getMessage());
$this->sendJsonResponse(false, 'Internal Server Error', ['status' => 'error'], 500);
}
}
public function handleRestoreRequests(string $action): void
{
$this->checkAdminAccess();
$type = $_GET['type'] ?? 'email';
$restoreType = RestoreType::tryFromInput($type);
if (!$restoreType) {
$this->jsonResponse(['status' => 'error', 'message' => 'Invalid table type'], 400);
return;
}
match ($action) {
'get_restore_requests' => $this->fetchRestoreRequests($restoreType),
'resolve_restore_request' => $this->resolveRestoreRequest($restoreType),
default => $this->jsonResponse(['status' => 'error', 'message' => 'Invalid Action'], 400),
};
}
private function fetchRestoreRequests(RestoreType $restoreType): void
{
try {
$table = $restoreType->getTargetTable();
$stmt = $this->pdo->query("SELECT * FROM {$table} ORDER BY id DESC");
$this->jsonResponse(['status' => 'success', 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);
} catch (PDOException $e) {
error_log("Database Error in fetchRestoreRequests: " . $e->getMessage());
$this->jsonResponse(['status' => 'error', 'message' => 'Internal Server Error'], 500);
}
}
private function resolveRestoreRequest(RestoreType $restoreType): void
{
$id = filter_var($this->inputData['id'] ?? $_GET['id'] ?? null, FILTER_VALIDATE_INT);
$statusType = $this->inputData['status'] ?? $_GET['status'] ?? 'approve';
if (!$id || $id <= 0) {
$this->jsonResponse(['status' => 'error', 'message' => 'Invalid ID'], 400);
return;
}
$allowedStatuses = [
'approve' => 'RESOLVED',
'reject' => 'REJECTED'
];
if (!array_key_exists($statusType, $allowedStatuses)) {
$this->jsonResponse(['status' => 'error', 'message' => 'Invalid status action requested'], 400);
return;
}
$newStatus = $allowedStatuses[$statusType];
$table = $restoreType->getTargetTable();
try {
$stmt = $this->pdo->prepare("UPDATE {$table} SET status = :status WHERE id = :id");
if ($stmt->execute([':status' => $newStatus, ':id' => $id])) {
$this->jsonResponse(['status' => 'success', 'message' => 'Request ' . strtolower($newStatus) . ' successfully']);
} else {
$this->jsonResponse(['status' => 'error', 'message' => 'Database update failed'], 500);
}
} catch (PDOException $e) {
error_log("Database Error in resolveRestoreRequest: " . $e->getMessage());
$this->jsonResponse(['status' => 'error', 'message' => 'Internal Server Error'], 500);
}
}
public function saveTimerSetting(): void
{
$this->checkAdminAccess();
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
$this->jsonResponse(['status' => 'error', 'message' => 'Invalid Request Method'], 405);
return;
}
try {
$duration = filter_var($this->inputData['duration'] ?? 5, FILTER_VALIDATE_INT);
$stmt = $this->pdo->prepare("INSERT INTO system_settings (setting_key, setting_value)
VALUES ('test_duration', :duration)
ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
if ($stmt->execute([':duration' => (string)$duration])) {
$this->jsonResponse(['status' => 'success', 'message' => 'Timer successfully saved!']);
} else {
$this->jsonResponse(['status' => 'error', 'message' => 'Database save failed'], 500);
}
} catch (PDOException $e) {
error_log("Database Error in saveTimerSetting: " . $e->getMessage());
$this->jsonResponse(['status' => 'error', 'message' => 'Internal Server Error'], 500);
}
}
public function getTimerSetting(): void
{
try {
$stmt = $this->pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'test_duration' LIMIT 1");
$stmt->execute();
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$duration = $row ? (int)$row['setting_value'] : 5;
$this->jsonResponse(['status' => 'success', 'duration' => $duration]);
} catch (PDOException $e) {
error_log("Database Error in getTimerSetting: " . $e->getMessage());
$this->jsonResponse(['status' => 'error', 'message' => 'Internal Server Error'], 500);
}
}
}
// ==================================================================
// 4. CLEAN API ROUTER SYSTEM
// ==================================================================
Config::init();
$pdo = Config::getDatabaseConnection();
$controller = new ApiController($pdo);
$action = $_GET['action'] ?? $controller->getInputData()['action'] ?? $_POST['action'] ?? '';
switch ($action) {
case 'register':
$controller->register();
break;
case 'login':
$controller->login();
break;
case 'logout':
$controller->logout();
break;
case 'get_user_data':
$controller->getUserData();
break;
case 'save_preferences':
$controller->savePreferences();
break;
case 'save_report':
$controller->saveReport();
break;
case 'get_questions':
$controller->getQuestions();
break;
case 'clear_reports':
$controller->clearReports();
break;
case 'update_profile':
$controller->updateProfile();
break;
case 'change_password':
$controller->changePassword();
break;
case 'submit_payment':
$controller->submitPayment();
break;
case 'get_payment_status':
$controller->getPaymentStatus();
break;
case 'verify_razorpay_payment':
$controller->verifyRazorpayPayment();
break;
case 'get_qr_image':
$controller->getQrImage();
break;
case 'forgot_password':
$controller->forgotPassword();
break;
case 'get_restore_requests':
case 'resolve_restore_request':
$controller->handleRestoreRequests($action);
break;
case 'save_timer':
$controller->saveTimerSetting();
break;
case 'get_timer':
$controller->getTimerSetting();
break;
case 'clear_attempt_history':
$controller->clearAttemptHistory();
break;
default:
http_response_code(404);
echo json_encode(['success' => false, 'message' => 'Invalid Action']);
break;
}




