<?php
require_once 'config.php'; // Ensures session_start(), autoloading, config vars ($clientID, etc.)

// We might still need SDK classes for constants or token refresh, but not for the main API call
use QuickBooksOnline\API\DataService\DataService;
use QuickBooksOnline\API\Core\OAuth2\OAuth2LoginHelper;

// --- Helper Function: Make API Call using cURL ---
/**
 * Makes an authenticated API call to QuickBooks Online using cURL.
 *
 * @param string $accessToken The valid OAuth2 Access Token.
 * @param string $url The full API endpoint URL (including query parameters).
 * @param string $method HTTP Method (e.g., 'GET', 'POST').
 * @param ?string $body Request body for POST/PUT requests (usually null for GET).
 * @param array &$responseHeaders Optional: Array to store response headers.
 * @return array ['statusCode' => int, 'body' => string|false, 'error' => ?string]
 */
function makeCurlApiCall(string $accessToken, string $url, string $method = 'GET', ?string $body = null, ?array &$responseHeaders = null): array {
    $responseHeaders = []; // Ensure it's an array
    $ch = curl_init();

    // --- Set cURL Options ---
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return response as string
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method)); // Set HTTP method
    curl_setopt($ch, CURLOPT_TIMEOUT, 30); // Request timeout
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); // Connection timeout
    // Store response headers
    curl_setopt($ch, CURLOPT_HEADERFUNCTION,
        function($curl, $header) use (&$responseHeaders) {
            $len = strlen($header);
            $header = explode(':', $header, 2);
            // Ignore invalid headers
            if (count($header) < 2) {
                return $len;
            }
            $responseHeaders[strtolower(trim($header[0]))][] = trim($header[1]);
            return $len;
        }
    );

    // --- Set Headers ---
    $headers = [
        'Authorization: Bearer ' . $accessToken,
        'Accept: application/json', // We want JSON responses
    ];
    if (strtoupper($method) === 'POST' || strtoupper($method) === 'PUT') {
        $headers[] = 'Content-Type: application/json'; // Assuming JSON body for POST/PUT
        if ($body !== null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }
    }
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    // --- Execute Request ---
    logError("cURL Call: Executing request", ['method' => $method, 'url' => $url]);
    $responseBody = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlErrorNum = curl_errno($ch);
    $curlErrorMsg = curl_error($ch);
    curl_close($ch);

    // --- Handle Response ---
    if ($curlErrorNum > 0) {
        logError("cURL Call: FAILED (cURL Error)", ['url' => $url, 'errno' => $curlErrorNum, 'error' => $curlErrorMsg]);
        return ['statusCode' => 0, 'body' => false, 'error' => "cURL Error ({$curlErrorNum}): {$curlErrorMsg}"];
    } else {
        logError("cURL Call: Completed", ['url' => $url, 'status' => $httpCode]);
        // Check HTTP status code for success (2xx)
        if ($httpCode >= 200 && $httpCode < 300) {
             return ['statusCode' => $httpCode, 'body' => $responseBody, 'error' => null];
        } else {
             logError("cURL Call: FAILED (HTTP Error)", ['url' => $url, 'status' => $httpCode, 'body' => substr($responseBody, 0, 500) . '...']);
             return ['statusCode' => $httpCode, 'body' => $responseBody, 'error' => "HTTP Error {$httpCode}"];
        }
    }
}

// --- Helper Function: Paginated Query using cURL ---
/**
 * Fetches all results for a QBO query endpoint using pagination via cURL.
 *
 * @param string $accessToken Valid Access Token.
 * @param string $realmId Company/Realm ID.
 * @param string $baseUrl The base URL ('Production' or 'Development').
 * @param string $entity The QBO entity type (e.g., 'Invoice', 'Bill').
 * @param string $filterClause The WHERE clause for the query (e.g., "TxnDate >= '...' AND TxnDate <= '...'").
 * @param int $maxResultsPerPage Max results per page (max 1000).
 * @return array All results combined.
 * @throws \Exception On critical cURL or API errors.
 */
function queryEntityWithPaginationCurl(string $accessToken, string $realmId, string $baseUrl, string $entity, string $filterClause, int $maxResultsPerPage = 1000): array {
     if ($maxResultsPerPage > 1000) $maxResultsPerPage = 1000;
     $allResults = [];
     $startPosition = 1;

     // Determine base API URL
     $qboApiBaseUrl = ($baseUrl === 'Production')
                      ? 'https://quickbooks.api.intuit.com'
                      : 'https://sandbox-quickbooks.api.intuit.com';
     $queryEndpoint = "/v3/company/{$realmId}/query";

     do {
        // Construct QL Query
        $query = "SELECT * FROM {$entity}";
        if (!empty($filterClause)) {
            $query .= " WHERE {$filterClause}";
        }
        $query .= " STARTPOSITION {$startPosition} MAXRESULTS {$maxResultsPerPage}";

        // Build URL with encoded query
        $url = $queryEndpoint . '?query=' . urlencode($query) . '&minorversion=70';
        $fullUrl = $qboApiBaseUrl . $url;

        logError("cURL Pagination Query", ['entity' => $entity, 'startPosition' => $startPosition, 'url' => $fullUrl]);

        // Make the API call using cURL helper
        $response = makeCurlApiCall($accessToken, $fullUrl);

        if ($response['error'] !== null || $response['body'] === false) {
            throw new \Exception("Failed to query {$entity} at position {$startPosition}. " . ($response['error'] ?? 'Unknown cURL error') . " Response Body: " . substr($response['body'] ?: '', 0, 500));
        }

        // Decode the JSON response
        $decoded = json_decode($response['body'], false); // Decode as object
        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new \Exception("Failed to decode JSON response for {$entity} at position {$startPosition}. Error: " . json_last_error_msg());
        }

        // Extract results from the QueryResponse object
        $results = $decoded->QueryResponse->$entity ?? null;

        if ($results === null || !is_array($results) || empty($results)) {
            logError("cURL Pagination Query: No more results.", ['entity' => $entity, 'startPosition' => $startPosition]);
            break; // No more data
        }

        $allResults = array_merge($allResults, $results);
        $resultCount = count($results);
        $startPosition += $resultCount;

        logError("cURL Pagination Query: Fetched page.", ['entity' => $entity, 'fetched' => $resultCount, 'totalSoFar' => count($allResults)]);
        // usleep(50000); // Optional delay

    } while ($resultCount === $maxResultsPerPage);

    logError("cURL Pagination Query: Finished.", ['entity' => $entity, 'totalFetched' => count($allResults)]);
    return $allResults;
}


// === Main Script Logic ===

// --- Authentication Check & Token Refresh ---
// (Keep the same logic as before to check session, check expiry, and refresh if needed)
// We MUST have a valid $accessTokenKey, $realmId, and $baseURL after this block
// ... (Auth/Refresh block from download_data.php) ...
// --- Ensure $accessTokenKey, $realmId, $baseURL are set ---
if (!isset($_SESSION['accessTokenKey']) || !isset($_SESSION['realmId'])) {
    $_SESSION['error'] = "Auth details missing. Please connect.";
    header('Location: index.php');
    exit;
}
$accessTokenKey = $_SESSION['accessTokenKey'];
$realmId = $_SESSION['realmId'];
// $baseURL should be set from config.php


// --- Define Overall Process Variables ---
$startYear = 2023;
$endYear = date('Y'); // Current year
$endMonth = date('m'); // Current month (as number 01-12)

$processLog = []; // Store results for each month
$overallErrors = []; // Store critical errors across months

logError("cURL Txn Backfill/Update: Starting process from {$startYear}-01 to {$endYear}-{$endMonth}", []);

// --- Loop Through Each Month ---
for ($year = $startYear; $year <= $endYear; $year++) {
    $monthLimit = ($year == $endYear) ? $endMonth : 12; // Loop up to current month in the final year

    for ($month = 1; $month <= $monthLimit; $month++) {
        $monthPadded = str_pad($month, 2, '0', STR_PAD_LEFT); // e.g., '01', '02', ..., '12'
        $fileSuffix = "{$year}-{$monthPadded}";
        $fileName = "transactions_curl_{$fileSuffix}.json";
        $filePath = DATA_DIR . '/' . $fileName;
        $periodDesc = date('F Y', strtotime("{$year}-{$monthPadded}-01"));

        $isCurrentMonth = ($year == $endYear && $month == $endMonth);
        $fileExists = file_exists($filePath);

        // --- Decision Logic ---
        if ($fileExists && !$isCurrentMonth) {
            // File exists for a past month - SKIP
            $processLog[$fileSuffix] = ['status' => 'SKIPPED', 'reason' => 'File exists for past month'];
            logError("cURL Txn Backfill/Update: Skipping month", ['period' => $periodDesc, 'file' => $fileName]);
            continue; // Move to the next month
        }

        if ($fileExists && $isCurrentMonth) {
            // File exists for the current month - DELETE before re-fetching
            logError("cURL Txn Backfill/Update: Deleting existing file for current month", ['period' => $periodDesc, 'file' => $fileName]);
            if (!unlink($filePath)) {
                 logError("cURL Txn Backfill/Update: FAILED to delete existing file", ['period' => $periodDesc, 'file' => $fileName]);
                 $processLog[$fileSuffix] = ['status' => 'FAILED', 'reason' => 'Could not delete existing file for current month.'];
                 $overallErrors[] = "{$periodDesc}: Could not delete existing file.";
                 continue; // Skip fetching for this month if delete failed
            }
        }

        // --- Fetch Data for the Month (if not skipped) ---
        logError("cURL Txn Backfill/Update: Processing month", ['period' => $periodDesc, 'file' => $fileName]);

        // Define date range for this specific month
        $startDate = "{$year}-{$monthPadded}-01";
        // For current month, end date is today; for past months, it's the last day
        $endDate = $isCurrentMonth ? date('Y-m-d') : date('Y-m-t', strtotime($startDate));

        $allTransactionsForMonth = [];
        $monthErrors = [];

        // --- Define Transaction Types to Fetch ---
        $txnTypesToFetch = [
            'Invoice', 'SalesReceipt', 'CreditMemo', 'RefundReceipt',
            'Bill', 'Purchase', 'VendorCredit',
            'Payment', 'JournalEntry'
        ];

        // --- Filter Clause based on Date ---
        $filterClause = "TxnDate >= '{$startDate}' AND TxnDate <= '{$endDate}'";

        // --- Loop Through Types and Fetch ---
        foreach ($txnTypesToFetch as $txnType) {
            try {
                // Check token expiry before potentially long loops (optional, refresh logic runs once at start)
                // Consider adding a check here if downloads take very long

                logError("cURL Txn Download: Fetching {$txnType} for {$periodDesc}...", []);
                $results = queryEntityWithPaginationCurl($accessTokenKey, $realmId, $baseURL, $txnType, $filterClause);
                if ($results) {
                    foreach ($results as $res) { if (is_object($res)) { $res->_EntityType = $txnType; } }
                    $allTransactionsForMonth = array_merge($allTransactionsForMonth, $results);
                    logError("cURL Txn Download: Fetched {$txnType} for {$periodDesc}", ['count' => count($results)]);
                } else {
                     logError("cURL Txn Download: No {$txnType} found for {$periodDesc}.", []);
                }
            } catch (\Throwable $e) {
                $errMsg = "Error fetching {$txnType} for {$periodDesc}: " . $e->getMessage();
                $monthErrors[] = $errMsg; // Record error for this month
                $overallErrors[] = $errMsg; // Add to overall errors
                logError("cURL Txn Download: FAILED fetching {$txnType} for {$periodDesc}", ['error' => $errMsg]);
                // Continue to next type
            }
        } // End foreach txnType

        // --- Save Combined Data for the Month ---
        $monthStatus = 'FAILED'; // Default status for the month
        $monthReason = '';
        $saveError = null;

        if (!empty($allTransactionsForMonth)) {
             logError("cURL Txn Backfill/Update: Saving combined data for {$periodDesc}...", ['file' => $fileName, 'count' => count($allTransactionsForMonth)]);
             $jsonData = json_encode($allTransactionsForMonth, JSON_PRETTY_PRINT);
             if ($jsonData === false) {
                 $saveError = "Failed to encode transactions to JSON for {$periodDesc}: " . json_last_error_msg();
             } elseif (file_put_contents($filePath, $jsonData) === false) {
                 $saveError = "Failed to write transactions to file: " . $filePath;
             }

             if ($saveError) {
                 $monthErrors[] = $saveError;
                 $overallErrors[] = $saveError;
                 logError("cURL Txn Backfill/Update: FAILED saving data for {$periodDesc}", ['error' => $saveError]);
                 $monthReason = $saveError;
             } else {
                 // If saving succeeded, status depends on whether fetch errors occurred
                 if (empty($monthErrors)) {
                     $monthStatus = 'SUCCESS';
                     $monthReason = count($allTransactionsForMonth) . " records saved.";
                 } else {
                     $monthStatus = 'PARTIAL_SUCCESS';
                     $monthReason = count($allTransactionsForMonth) . " records saved, but fetch errors occurred.";
                 }
                 logError("cURL Txn Backfill/Update: Data saved for {$periodDesc}.", ['file' => $filePath]);
             }
        } else {
             logError("cURL Txn Backfill/Update: No transactions fetched to save for {$periodDesc}.", []);
             if (empty($monthErrors)) {
                  $monthStatus = 'SUCCESS'; // No errors, just no data
                  $monthReason = "No transactions found.";
             } else {
                  $monthStatus = 'FAILED'; // Fetch errors occurred, and no data saved
                  $monthReason = "No data saved due to fetch errors.";
             }
        }
        // Record the outcome for this month
        $processLog[$fileSuffix] = ['status' => $monthStatus, 'reason' => $monthReason];

    } // End for month
} // End for year


// --- Set Final Session Messages Based on Overall Process ---
$successCount = count(array_filter($processLog, function($log) {
    return $log['status'] === 'SUCCESS' || $log['status'] === 'PARTIAL_SUCCESS';
}));
$skipCount = count(array_filter($processLog, function($log) {
    return $log['status'] === 'SKIPPED';
}));
$failCount = count(array_filter($processLog, function($log) {
    return $log['status'] === 'FAILED';
}));
$totalMonths = count($processLog);

$finalMessage = "Transaction Backfill/Update Process Complete.\n";
$finalMessage .= " - Months Processed/Attempted: " . ($totalMonths - $skipCount) . "\n";
$finalMessage .= " - Months Successfully Saved (or Partially): {$successCount}\n";
$finalMessage .= " - Months Skipped (Already Existed): {$skipCount}\n";
$finalMessage .= " - Months Failed: {$failCount}";

$_SESSION['message'] = $finalMessage;

if (!empty($overallErrors)) {
     $_SESSION['error'] = "Errors occurred during the process. Check logs for details. Summary: " . implode('; ', array_slice($overallErrors, 0, 3)) . (count($overallErrors)>3 ? '...' : ''); // Show first few errors
} else {
    unset($_SESSION['error']); // Clear previous errors if none occurred this time
}

logError("cURL Txn Backfill/Update: Process Finished.", ['summary' => $processLog]);


header('Location: index.php');
exit;

?>