<?php
/**
 * MT5 Trade Reconciler API
 * POST /api/mt5/reconcile
 *
 * Rebuilt 2026-08-19 alongside src/Mt5ReportParser.php. The old
 * implementation here wrote to columns that do not exist on `trades`
 * (`mt5_ticket`, `actual_tp_price` - the real columns are
 * `mt5_position_id` and there is no broker TP column at all), which
 * meant every single reconciliation write - matched or manually
 * confirmed - threw a real SQLSTATE[42S22] and silently failed. It also
 * never wrote to any of the three tables this schema actually has for
 * this (`broker_reconciliations`, `unmatched_broker_trades`,
 * `mql5_trades`), so nothing about an upload - matched, unmatched, or
 * "this looks like a trade you never journaled" - ever survived past the
 * one browser session that produced it.
 *
 * This version matches against ONE account's trades only (never
 * user-wide - a cross-account match is a data-integrity bug waiting to
 * happen, not a feature), persists every genuinely unmatched MT5
 * position into unmatched_broker_trades so it survives a reload, and
 * scores matches using the MT5 position's real, netted P&L
 * (profit + commission + swap - confirmed against a real statement that
 * this, not the raw "Profit" column alone, is what actually moves the
 * account's balance) as a strong new signal alongside instrument/
 * direction/time/size/price.
 */

require_once __DIR__ . '/../../config/config.php';
require_once __DIR__ . '/../../src/ScopedDb.php';
require_once __DIR__ . '/../../src/Auth.php';
require_once __DIR__ . '/../../src/PropFirmHelpers.php';
require_once __DIR__ . '/../../src/TradeClassificationEngine.php';
require_once __DIR__ . '/../../src/Mt5ReportParser.php';
require_once __DIR__ . '/../../src/Mt5Reconciler.php';
require_once __DIR__ . '/../../src/Csrf.php';

$auth = new Auth($pdo);
$userId = $auth->requireAuth();

$tier = 'free';
try {
    $stmt = $pdo->prepare('SELECT tier FROM user_profiles WHERE user_id = :uid LIMIT 1');
    $stmt->execute([':uid' => $userId]);
    $profileRow = $stmt->fetch(PDO::FETCH_ASSOC);
    if ($profileRow && !empty($profileRow['tier'])) $tier = $profileRow['tier'];
} catch (\Throwable $e) {
    error_log("Failed to read user tier: " . $e->getMessage());
}

if (!in_array($tier, ['pro', 'elite', 'ultimate'], true)) {
    respond(403, ['success' => false, 'error' => 'MT5 reconciliation requires Pro tier or higher.']);
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    respond(405, ['success' => false, 'error' => 'Method not allowed. Use POST.']);
}

if (!Csrf::verify()) {
    respond(403, ['success' => false, 'error' => 'Your session expired - please refresh the page and try again.']);
}

$input = json_decode(file_get_contents('php://input'), true);
if (!$input || !is_array($input)) {
    respond(400, ['success' => false, 'error' => 'Invalid request body.']);
}

// ── Manual confirmation / skip actions (used by mt5-resolve.php) ──
if (isset($input['action'])) {
    handleAction($pdo, $userId, $input);
    exit;
}

$positions = $input['positions'] ?? $input['trades'] ?? [];
$accountId = $input['account_id'] ?? null;
$reconciliationId = $input['reconciliation_id'] ?? null;

if (!$accountId) {
    respond(400, ['success' => false, 'error' => 'account_id is required.']);
}

$acctCheck = $pdo->prepare('SELECT id, account_type, account_number, broker_login, broker_server FROM trading_accounts WHERE id = :id AND user_id = :uid AND deleted_at IS NULL LIMIT 1');
$acctCheck->execute([':id' => $accountId, ':uid' => $userId]);
$accountRow = $acctCheck->fetch(PDO::FETCH_ASSOC);
if (!$accountRow) {
    respond(404, ['success' => false, 'error' => 'Account not found.']);
}

if ($reconciliationId) {
    $reconCheck = $pdo->prepare('SELECT id, source_file_url, source_file_format FROM broker_reconciliations WHERE id = :id AND user_id = :uid AND account_id = :aid LIMIT 1');
    $reconCheck->execute([':id' => $reconciliationId, ':uid' => $userId, ':aid' => $accountId]);
    $reconRow = $reconCheck->fetch(PDO::FETCH_ASSOC);
    if (!$reconRow) {
        $reconciliationId = null; // don't trust a mismatched/foreign id, just proceed without audit linkage
    }
}

// ── Real, server-side account-identity gate, mandatory ───────────────
// Never trusts the client's own opinion of whether the statement matches
// (a crafted or stale request could claim anything, and the account
// picked at upload time could simply have been the wrong one) - re-reads
// the ACTUAL saved upload file this reconciliation_id points at and
// re-derives the match independently via the same Login+Server check
// api/mt5/parse.php uses. Nothing reconciles without a real
// 'verified_match' - no reconciliation_id, no readable file, a real
// mismatch, or a login that simply was never saved on this account all
// refuse identically. This closes two real, separately-found flaws: a
// prop-firm account with no saved login was reconciled against an
// unrelated FBS demo statement because the old check had no signal to
// compare and silently reported "matches" anyway, and a later version
// let a wrong account get upgraded to a permanent false "verified" match
// via a one-click save button during the upload flow itself - both are
// gone now. Confirming an account's real login (and, when known, its
// broker server) can only happen deliberately, in Active Journeys /
// Trading Center's own account-details editor, never as a side effect of
// this endpoint or of the upload page.
if (!$reconciliationId || empty($reconRow['source_file_url'])) {
    respond(409, ['success' => false, 'error' => 'This upload has no verifiable record to reconcile against. Upload the statement again.', 'account_mismatch' => true]);
}
$filePath = __DIR__ . '/../../' . $reconRow['source_file_url'];
if (!is_file($filePath)) {
    respond(409, ['success' => false, 'error' => 'The original uploaded statement could not be found. Upload it again.', 'account_mismatch' => true]);
}
$ext = $reconRow['source_file_format'] === 'mt5_csv' ? 'csv' : pathinfo($filePath, PATHINFO_EXTENSION);
$reparsed = Mt5ReportParser::parse(file_get_contents($filePath), $ext ?: 'html');
if (!$reparsed['success']) {
    respond(409, ['success' => false, 'error' => 'The saved statement could not be re-verified. Upload it again.', 'account_mismatch' => true]);
}
$identity = Mt5ReportParser::matchAccountIdentity($reparsed['account'], $accountRow['account_number'], $accountRow['broker_server'], $accountRow['broker_login']);
if ($identity['status'] !== 'verified_match') {
    $error = $identity['status'] === 'unverified'
        ? 'This account has no saved broker login yet, so this statement cannot be verified as belonging to it. Add the account\'s real MT5 login (and broker server) first.'
        : ('This statement belongs to broker login #' . $identity['statement_account_number']
            . ($identity['statement_server'] ? ' on ' . $identity['statement_server'] : '')
            . ', not the account you selected (#' . ($identity['saved_account_number'] ?? '?') . '). Choose the correct account or upload the right statement.');
    respond(409, ['success' => false, 'error' => $error, 'account_mismatch' => true]);
}

if (empty($positions)) {
    respond(200, [
        'success' => true, 'matched' => [], 'unmatched_mt5' => [], 'unmatched_traderalign' => [],
        'needs_confirmation' => [], 'message' => 'No positions to reconcile.',
    ]);
}

try {
    $reconciler = new MT5Reconciler($pdo, $userId, $accountId, $reconciliationId);
    $result = $reconciler->reconcile($positions);
    respond(200, array_merge(['success' => true], $result));
} catch (\Throwable $e) {
    error_log("MT5 reconcile error: " . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine());
    respond(500, ['success' => false, 'error' => 'Reconciliation failed. Please try again.']);
}

// ── Manual actions: confirm a pair, skip/dismiss a broker row, create trade ──
function handleAction(PDO $pdo, string $userId, array $input): void
{
    $action = $input['action'];

    if ($action === 'confirm' && isset($input['traderalign_id'], $input['mt5_position'])) {
        $accountId = $input['account_id'] ?? null;
        $acctCheck = $pdo->prepare('SELECT id FROM trading_accounts WHERE id = :id AND user_id = :uid AND deleted_at IS NULL LIMIT 1');
        $acctCheck->execute([':id' => $accountId, ':uid' => $userId]);
        if (!$acctCheck->fetch()) {
            respond(404, ['success' => false, 'error' => 'Account not found.']);
        }
        $reconciler = new MT5Reconciler($pdo, $userId, $accountId, $input['reconciliation_id'] ?? null);
        $ok = $reconciler->applyReconciliation($input['traderalign_id'], $input['mt5_position']);

        if ($ok && !empty($input['unmatched_broker_trade_id'])) {
            $upd = $pdo->prepare("UPDATE unmatched_broker_trades SET resolution = 'linked_manually', resolved_trade_id = :tid, resolved_at = NOW() WHERE id = :id AND user_id = :uid");
            $upd->execute([':tid' => $input['traderalign_id'], ':id' => $input['unmatched_broker_trade_id'], ':uid' => $userId]);
        }

        respond(200, ['success' => $ok, 'error' => $ok ? null : 'Failed to apply reconciliation.']);
    }

    if ($action === 'dismiss' && isset($input['unmatched_broker_trade_id'])) {
        $resolution = in_array($input['resolution'] ?? '', ['dismissed_ghost', 'dismissed_other_account'], true)
            ? $input['resolution'] : 'dismissed_ghost';
        $upd = $pdo->prepare("UPDATE unmatched_broker_trades SET resolution = :res, resolved_at = NOW() WHERE id = :id AND user_id = :uid");
        $upd->execute([':res' => $resolution, ':id' => $input['unmatched_broker_trade_id'], ':uid' => $userId]);
        respond(200, ['success' => $upd->rowCount() > 0]);
    }

    respond(400, ['success' => false, 'error' => 'Unknown or incomplete action.']);
}

function respond(int $code, array $data): void {
    http_response_code($code);
    header('Content-Type: application/json');
    echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
    exit;
}
