<?php
// /traderalign/api/accounts/create.php
//
// Creates a trading_accounts row + matching challenge_accounts row for new prop firm attempts.
// Handles auto-attempt-numbering: if the user already has account(s) at the
// same prop firm + same size, this one becomes "Attempt N" automatically.
//
// Also handles phase progression: promoting an existing account from
// phase_1_complete → phase_2_active (or funded_active for one_step),
// phase_2_complete → phase_3_active (three_step) or funded_active,
// phase_3_complete → funded_active.
// Phase promotions and funded activation reset challenge_accounts.current_balance
// to account_size so each stage starts from the full account size.
// trading_accounts.current_balance is never reset here (live MT5 ledger).
//
// Also handles live/personal account creation with auto-generated display IDs.

header('Content-Type: application/json');

$userId = $auth->requireAuth();

// 2026-08-17 account-level workflow isolation: whichever workflow the user
// is CURRENTLY in at the moment of creation permanently owns this account
// (trading_accounts.workflow_context) — never re-derived from trade
// history, never touched again after creation (promotion, below, mutates
// the SAME row's lifecycle_state, it does not create a new row and must
// never touch this field). See migration_add_account_workflow_context.sql.
$accountWorkflowContext = ValidationService::getWorkflowState($userId, $db);

$input = json_decode(file_get_contents('php://input'), true);
if (!$input) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid request body']);
    exit;
}

// Determine mode: 'promote' for prop firm phase progression, 'live' for personal accounts
$mode = trim($input['mode'] ?? '');
$accountType = trim($input['account_type'] ?? '');

// ── Live / Personal Account Creation ──
if ($mode === 'live' || $accountType === 'personal') {
    $brokerName = trim($input['broker_name'] ?? '');
    $accountLabel = trim($input['account_label'] ?? '');
    $initialDeposit = (float)($input['initial_deposit'] ?? 0);
    $currency = strtoupper(trim($input['currency'] ?? 'USD'));
    if (!CurrencyService::isSupported($currency)) { $currency = 'USD'; }
    
    // Validation
    if ($brokerName === '') {
        http_response_code(422);
        echo json_encode(['error' => 'broker_name is required']);
        exit;
    }
    if ($accountLabel === '') {
        http_response_code(422);
        echo json_encode(['error' => 'account_label is required']);
        exit;
    }
    if ($initialDeposit <= 0) {
        http_response_code(422);
        echo json_encode(['error' => 'initial_deposit must be greater than 0']);
        exit;
    }
    
    try {
        $pdo->beginTransaction();
        
        // Generate lifecycle_display_id for this broker
        // Query the highest existing display_id for this broker (case-insensitive)
        $existingIds = $db->raw(
            "SELECT lifecycle_display_id 
             FROM trading_accounts 
             WHERE user_id = :uid 
               AND account_type = 'personal' 
               AND lifecycle_display_id LIKE :pattern
               AND deleted_at IS NULL
             ORDER BY lifecycle_display_id DESC 
             LIMIT 1",
            [
                ':uid' => $userId,
                ':pattern' => strtoupper($brokerName) . '-%'
            ]
        );
        
        $nextNumber = 1;
        if (!empty($existingIds) && !empty($existingIds[0]['lifecycle_display_id'])) {
            // Extract the numeric part from "BROKER-0001"
            $lastId = $existingIds[0]['lifecycle_display_id'];
            if (preg_match('/-(\d+)$/', $lastId, $matches)) {
                $nextNumber = (int)$matches[1] + 1;
            }
        }
        
        $lifecycleDisplayId = strtoupper($brokerName) . '-' . str_pad($nextNumber, 4, '0', STR_PAD_LEFT);
        
        // Build label: "HFM - Main Trading Account"
        $label = $brokerName . ' - ' . $accountLabel;
        
        // Create the trading account
        // NOTE: prop_firm_name must be NULL for personal accounts due to CHECK constraint:
        //   (account_type = 'personal' AND prop_firm_name IS NULL) OR 
        //   (account_type = 'prop_firm' AND prop_firm_name IS NOT NULL)
        $accountId = $db->uuid();
        $db->insert('trading_accounts', [
            'id'                    => $accountId,
            'user_id'               => $userId,
            'workflow_context'      => $accountWorkflowContext,
            'label'                 => $label,
            'account_type'          => 'personal',
            'prop_firm_name'        => null,
            'starting_balance'      => $initialDeposit,
            'current_balance'       => $initialDeposit,
            'initial_deposit'       => $initialDeposit,
            'currency'              => $currency,
            'lifecycle_state'       => 'active',
            'lifecycle_display_id'  => $lifecycleDisplayId,
            'is_active'             => 1,
        ]);
        
        // Insert lifecycle event for initial deposit
        $eventId = $db->uuid();
        $stmt = $pdo->prepare(
            "INSERT INTO lifecycle_events (id, account_id, account_type, event_type, occurred_at)
             VALUES (:id, :account_id, :account_type, :event_type, NOW())"
        );
        $stmt->execute([
            ':id'           => $eventId,
            ':account_id'   => $accountId,
            ':account_type' => 'live',
            ':event_type'   => 'INITIAL_DEPOSIT',
        ]);
        
        $pdo->commit();
        
        echo json_encode([
            'success'               => true,
            'account_id'            => $accountId,
            'lifecycle_display_id'  => $lifecycleDisplayId,
            'label'                 => $label,
            'broker_name'           => $brokerName,
            'initial_deposit'       => $initialDeposit,
            'currency'              => $currency,
            'lifecycle_state'       => 'active',
            'account_type'          => 'personal',
        ]);
        exit;
    } catch (Exception $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        error_log('accounts/create.php (live account) failed: ' . $e->getMessage());
        http_response_code(500);
        echo json_encode(['error' => 'Failed to create account. Please try again.']);
        exit;
    }
}

// ── Phase Progression Mode ──
if ($mode === 'promote') {
    $existingAccountId = trim($input['account_id'] ?? '');
    $currentLifecycleState = trim($input['current_lifecycle_state'] ?? '');
    
    if ($existingAccountId === '') {
        http_response_code(422);
        echo json_encode(['error' => 'account_id is required for promotion']);
        exit;
    }

    try {
        $pdo->beginTransaction();

        // Fetch the existing challenge account and lock it
        $existing = $db->raw(
            "SELECT ca.*, ta.label as display_name
             FROM challenge_accounts ca
             JOIN trading_accounts ta ON ca.account_id = ta.id
             WHERE ca.account_id = :account_id 
               AND ca.user_id = :uid 
               AND ca.deleted_at IS NULL
             FOR UPDATE",
            [':account_id' => $existingAccountId, ':uid' => $userId]
        );

        if (empty($existing)) {
            $pdo->rollBack();
            http_response_code(404);
            echo json_encode(['error' => 'Account not found']);
            exit;
        }

        $account = $existing[0];
        $currentState = $account['lifecycle_state'];
        $rulesDecoded = json_decode($account['rules'] ?? '{}', true) ?: [];
        $challengeType = $account['challenge_type'] ?? ($rulesDecoded['challenge_type'] ?? 'two_step');
        if ($challengeType === 'instant') {
            $challengeType = 'instant_funding';
        }
        $accountSize = (float) ($account['account_size'] ?? 0);
        $nextState = null;
        $nextStage = null;
        $eventType = null;

        // Validate current state and determine next state
        if ($currentState === 'phase_1_complete') {
            if ($challengeType === 'one_step') {
                $nextState = 'funded_active';
                $eventType = 'FUNDED_ACTIVATED';
            } else {
                $nextState = 'phase_2_active';
                $eventType = 'PHASE_2_STARTED';
            }
        } elseif ($currentState === 'phase_2_complete') {
            // three_step (if present) advances to phase 3; two_step goes funded
            if ($challengeType === 'three_step') {
                $nextState = 'phase_3_active';
                $eventType = 'PHASE_3_STARTED';
            } else {
                $nextState = 'funded_active';
                $eventType = 'FUNDED_ACTIVATED';
            }
        } elseif ($currentState === 'phase_3_complete') {
            $nextState = 'funded_active';
            $eventType = 'FUNDED_ACTIVATED';
        } else {
            $pdo->rollBack();
            http_response_code(422);
            echo json_encode(['error' => "Account is not eligible for promotion. Current state: {$currentState}. Must be phase_1_complete, phase_2_complete, or phase_3_complete."]);
            exit;
        }

        if ($nextState === 'phase_2_active') {
            $nextStage = 'phase_2';
        } elseif ($nextState === 'phase_3_active') {
            $nextStage = 'phase_3';
        } elseif ($nextState === 'funded_active') {
            $nextStage = 'funded';
        }

        // Phase promotions and funded activation all start fresh at account_size.
        // This is the ONE-TIME reset when promoting TO funded. After this, funded balance only changes from trade outcomes.
        $resetChallengeBalance = in_array($nextState, ['phase_2_active', 'phase_3_active', 'funded_active'], true);

        if ($resetChallengeBalance && $accountSize <= 0) {
            $pdo->rollBack();
            http_response_code(422);
            echo json_encode(['error' => 'Cannot reset challenge balance: account_size is missing or invalid']);
            exit;
        }

        if ($resetChallengeBalance) {
            $stmt = $pdo->prepare(
                "UPDATE challenge_accounts 
                 SET lifecycle_state = :next_state,
                     current_stage = :next_stage,
                     current_balance = :account_size,
                     updated_at = NOW()
                 WHERE account_id = :account_id 
                   AND user_id = :uid 
                   AND deleted_at IS NULL"
            );
            $stmt->execute([
                ':next_state'    => $nextState,
                ':next_stage'    => $nextStage,
                ':account_size'  => $accountSize,
                ':account_id'    => $existingAccountId,
                ':uid'           => $userId,
            ]);
        } else {
            $stmt = $pdo->prepare(
                "UPDATE challenge_accounts 
                 SET lifecycle_state = :next_state,
                     current_stage = :next_stage,
                     updated_at = NOW()
                 WHERE account_id = :account_id 
                   AND user_id = :uid 
                   AND deleted_at IS NULL"
            );
            $stmt->execute([
                ':next_state' => $nextState,
                ':next_stage' => $nextStage,
                ':account_id' => $existingAccountId,
                ':uid' => $userId,
            ]);
        }

        if ($stmt->rowCount() === 0) {
            $pdo->rollBack();
            http_response_code(500);
            echo json_encode(['error' => 'Failed to update account state']);
            exit;
        }

        $taStmt = $pdo->prepare(
            "UPDATE trading_accounts
             SET lifecycle_state = :next_state, updated_at = NOW()
             WHERE id = :account_id
               AND user_id = :uid
               AND deleted_at IS NULL"
        );
        $taStmt->execute([
            ':next_state' => $nextState,
            ':account_id' => $existingAccountId,
            ':uid' => $userId,
        ]);

        if ($taStmt->rowCount() === 0) {
            $pdo->rollBack();
            http_response_code(500);
            echo json_encode(['error' => 'Failed to update trading account state']);
            exit;
        }

        // Journal refinement item 9 — a prop firm typically issues new
        // platform login credentials on phase promotion/funded activation
        // (a genuinely new account, per the prop firm's own systems, even
        // though this app models it as the same trading_accounts row).
        // $resetChallengeBalance (set above) is true only for a real
        // phase_2/phase_3/funded promotion — Instant Funding never runs
        // through this 'promote' branch at all (it has no phases), so its
        // credentials are never touched by this reset, matching the brief
        // exactly. Tags and account_notes are deliberately NOT reset here —
        // they describe the trader's own relationship to the account
        // across its whole life, not broker-issued credentials.
        if ($resetChallengeBalance) {
            $credStmt = $pdo->prepare(
                "UPDATE trading_accounts
                 SET platform = NULL, broker_server = NULL, broker_login = NULL, broker_password_encrypted = NULL, account_number = NULL
                 WHERE id = :account_id AND user_id = :uid AND deleted_at IS NULL"
            );
            $credStmt->execute([':account_id' => $existingAccountId, ':uid' => $userId]);
        }

        // Insert lifecycle event
        $eventId = $db->uuid();
        $stmt = $pdo->prepare(
            "INSERT INTO lifecycle_events (id, account_id, account_type, event_type, occurred_at)
             VALUES (:id, :account_id, :account_type, :event_type, NOW())"
        );
        $stmt->execute([
            ':id' => $eventId,
            ':account_id' => $existingAccountId,
            ':account_type' => 'prop_firm',
            ':event_type' => $eventType,
        ]);

        $pdo->commit();

        echo json_encode([
            'success' => true,
            'account_id' => $existingAccountId,
            'challenge_account_id' => $account['id'],
            'lifecycle_state' => $nextState,
            'previous_state' => $currentState,
            'event_type' => $eventType,
            'attempt_number' => $account['attempt_number'],
            'display_label' => $account['display_name'],
            'prop_firm_name' => $account['prop_firm_name'],
            'account_size' => $account['account_size'],
        ]);
        exit;
    } catch (Exception $e) {
        if ($pdo->inTransaction()) {
            $pdo->rollBack();
        }
        error_log('accounts/create.php (promote) failed: ' . $e->getMessage());
        http_response_code(500);
        echo json_encode(['error' => 'Failed to promote account. Please try again.']);
        exit;
    }
}

// ── New Prop Firm Attempt Mode (existing logic) ──
$propFirmName  = trim($input['prop_firm_name'] ?? '');
$accountSize   = $input['account_size'] ?? null;
$challengeType = $input['challenge_type'] ?? null;
$nickname      = trim($input['nickname'] ?? '');
$rules         = $input['rules'] ?? [];
$customRules   = $input['custom_rules'] ?? [];
$leverage      = trim($input['leverage'] ?? '');

$validTypes = ['two_step', 'one_step', 'three_step', 'instant_funding'];

if ($propFirmName === '' || !$accountSize || !in_array($challengeType, $validTypes, true)) {
    http_response_code(422);
    echo json_encode(['error' => 'prop_firm_name, account_size, and a valid challenge_type are required']);
    exit;
}

// Validate rules shape per challenge type. Missing optional fields are fine —
// they're stored as null — but the required ones must be present and numeric.
$requiredFieldsByType = [
    'two_step'        => ['phase_1_target_pct', 'phase_2_target_pct', 'daily_dd_pct', 'max_dd_pct'],
    'three_step'      => ['phase_1_target_pct', 'phase_2_target_pct', 'phase_3_target_pct', 'daily_dd_pct', 'max_dd_pct'],
    'one_step'        => ['target_pct', 'daily_dd_pct', 'max_dd_pct'],
    'instant_funding' => ['daily_dd_pct', 'max_dd_pct'],
];

function challengeTypeForRulesJson(string $challengeType): string
{
    if ($challengeType === 'instant_funding') {
        return 'instant';
    }

    return $challengeType;
}

foreach ($requiredFieldsByType[$challengeType] as $field) {
    if (!isset($rules[$field]) || $rules[$field] === '' || !is_numeric($rules[$field])) {
        http_response_code(422);
        echo json_encode(['error' => "Missing or invalid required rule field: {$field}"]);
        exit;
    }
}

// ── Maximum Loss Model / Consistency Rule / Daily Reset (2026-08-17) ──
// All three are optional, additive rules-JSON keys — PropFirmRuleEngine
// already defensively normalizes an unrecognized max_loss_model to
// 'fixed' and only ever engages the reset-time/consistency logic when
// its own keys are fully present and well-formed, so a malformed value
// here can never produce dangerous behavior downstream. This validation
// exists purely to reject a bad value at creation time with a clear
// error, rather than silently storing something the engine will later
// just ignore. Only 'fixed'/'trailing' are recognized — a third
// "Smart/Step" model was considered and explicitly omitted (not an
// established prop-firm drawdown model), so it's neither accepted here
// nor offered by this endpoint's own UI.
$maxLossModel = isset($rules['max_loss_model']) ? strtolower(trim((string)$rules['max_loss_model'])) : null;
if ($maxLossModel !== null && !in_array($maxLossModel, ['fixed', 'trailing'], true)) {
    http_response_code(422);
    echo json_encode(['error' => "Invalid max_loss_model: {$maxLossModel}. Must be 'fixed' or 'trailing'."]);
    exit;
}

$resetTimeRaw = isset($rules['daily_reset_time']) ? trim((string)$rules['daily_reset_time']) : '';
$resetTzRaw = isset($rules['daily_reset_timezone']) ? trim((string)$rules['daily_reset_timezone']) : '';
if (($resetTimeRaw !== '') !== ($resetTzRaw !== '')) {
    http_response_code(422);
    echo json_encode(['error' => 'Daily reset time and timezone must both be provided, or both left blank.']);
    exit;
}
if ($resetTimeRaw !== '') {
    if (!preg_match('/^([01]?\d|2[0-3]):([0-5]\d)$/', $resetTimeRaw)) {
        http_response_code(422);
        echo json_encode(['error' => 'Invalid daily_reset_time, expected HH:MM (24-hour).']);
        exit;
    }
    if (!in_array($resetTzRaw, DateTimeZone::listIdentifiers(), true)) {
        http_response_code(422);
        echo json_encode(['error' => "Invalid daily_reset_timezone: {$resetTzRaw}"]);
        exit;
    }
}

$consistencyMethodRaw = isset($rules['consistency_method']) ? trim((string)$rules['consistency_method']) : '';
$consistencyMaxPctRaw = $rules['consistency_max_pct'] ?? null;
$consistencyMaxPctProvided = $consistencyMaxPctRaw !== null && $consistencyMaxPctRaw !== '';
if (($consistencyMethodRaw !== '') !== $consistencyMaxPctProvided) {
    http_response_code(422);
    echo json_encode(['error' => 'Consistency method and maximum allowed % must both be provided, or both left blank.']);
    exit;
}
if ($consistencyMethodRaw !== '') {
    if ($consistencyMethodRaw !== 'biggest_day_over_total_profit') {
        http_response_code(422);
        echo json_encode(['error' => "Unrecognized consistency_method: {$consistencyMethodRaw}"]);
        exit;
    }
    if (!is_numeric($consistencyMaxPctRaw) || (float)$consistencyMaxPctRaw <= 0 || (float)$consistencyMaxPctRaw > 100) {
        http_response_code(422);
        echo json_encode(['error' => 'consistency_max_pct must be a number greater than 0 and at most 100.']);
        exit;
    }
}

// Ensure account_size is a valid positive number
$startingBalance = (float)($input['account_size'] ?? 0);
if ($startingBalance <= 0) {
    http_response_code(422);
    echo json_encode(['error' => 'account_size must be greater than 0']);
    exit;
}

try {
    $pdo->beginTransaction();

    // ── Auto-attempt numbering ──
    // Look at existing (non-deleted) challenge_accounts for this user with the same
    // firm name (case-insensitive) and same account size, take the max attempt_number, +1.
    $existing = $db->raw(
        "SELECT MAX(attempt_number) AS max_attempt
         FROM challenge_accounts
         WHERE user_id = :uid AND LOWER(prop_firm_name) = LOWER(:firm) AND account_size = :size AND deleted_at IS NULL",
        [':uid' => $userId, ':firm' => $propFirmName, ':size' => $startingBalance]
    );
    $attemptNumber = (int)($existing[0]['max_attempt'] ?? 0) + 1;

    // Build display label: "FTMO 10K - Attempt 2" unless a nickname overrides it
    $sizeLabel = $startingBalance >= 1000
        ? rtrim(rtrim(number_format($startingBalance / 1000, 1), '0'), '.') . 'K'
        : (string)$startingBalance;
    $autoLabel = "{$propFirmName} {$sizeLabel} - Attempt {$attemptNumber}";
    $displayLabel = $nickname !== '' ? $nickname : $autoLabel;

    $accountId = $db->uuid();
    
    // Instant Funding starts at funded_active, not phase_1_active (no phases)
    $initialLifecycleState = ($challengeType === 'instant_funding') ? 'funded_active' : 'phase_1_active';
    
    $db->insert('trading_accounts', [
        'id'                => $accountId,
        'user_id'           => $userId,
        'workflow_context'  => $accountWorkflowContext,
        'label'             => $displayLabel,
        'account_type'      => 'prop_firm',
        'prop_firm_name'    => $propFirmName,
        'starting_balance'  => $startingBalance,
        'current_balance'   => $startingBalance,
        'lifecycle_state'   => $initialLifecycleState,
        'is_active'         => 1,
    ]);

    $rulesForStorage = $rules;
    $rulesForStorage['challenge_type'] = challengeTypeForRulesJson($challengeType);

    $challengeAccountId = $db->uuid();
    
    // Instant Funding starts at funded_active, not phase_1_active (no phases)
    $initialLifecycleState = ($challengeType === 'instant_funding') ? 'funded_active' : 'phase_1_active';
    $initialStage = ($challengeType === 'instant_funding') ? 'funded' : 'phase_1';
    
    $db->insert('challenge_accounts', [
        'id'              => $challengeAccountId,
        'account_id'      => $accountId,
        'user_id'         => $userId,
        'prop_firm_name'  => $propFirmName,
        'account_size'    => $startingBalance,
        'challenge_type'  => $challengeType,
        'attempt_number'  => $attemptNumber,
        'nickname'        => $nickname !== '' ? $nickname : null,
        'lifecycle_state' => $initialLifecycleState,
        'current_stage'   => $initialStage,
        'status'          => 'active',
        'rules'           => json_encode($rulesForStorage),
        'custom_rules'    => !empty($customRules) ? json_encode($customRules) : null,
        'leverage'        => $leverage !== '' ? $leverage : null,
    ]);

    // Insert lifecycle event for account creation
    $eventId = $db->uuid();
    $stmt = $pdo->prepare(
        "INSERT INTO lifecycle_events (id, account_id, account_type, event_type, occurred_at)
         VALUES (:id, :account_id, :account_type, :event_type, NOW())"
    );
    $stmt->execute([
        ':id' => $eventId,
        ':account_id' => $accountId,
        ':account_type' => 'prop_firm',
        ':event_type' => 'ACCOUNT_CREATED',
    ]);

    $pdo->commit();

    echo json_encode([
        'account_id'           => $accountId,
        'challenge_account_id' => $challengeAccountId,
        'display_label'        => $displayLabel,
        'attempt_number'       => $attemptNumber,
        'lifecycle_state'      => $initialLifecycleState,
        'leverage'             => $leverage !== '' ? $leverage : null,
    ]);
} catch (Exception $e) {
    if ($pdo->inTransaction()) {
        $pdo->rollBack();
    }
    error_log('accounts/create.php (new prop attempt) failed: ' . $e->getMessage());
    http_response_code(500);
    echo json_encode(['error' => 'Failed to create account. Please try again.']);
}