import type { TradeExecutor } from './TradeExecutor.js';
import { SignalDirection } from '../strategies/rules/types.js';
import logger from '../utils/logger.js';
import { supabaseService } from './SupabaseService.js';
import { config } from '../config/index.js';

const CAPITAL_WAIT_TIMEOUT_MS = 60_000;
const CAPITAL_WAIT_POLL_MS = 3_000;

export class ManualTrader {
    constructor(
        private executor: TradeExecutor
    ) { }

    private sleep(ms: number): Promise<void> {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }

    private roundDown(value: number, precision: number): number {
        const safePrecision = Math.max(0, Math.min(10, precision));
        const factor = Math.pow(10, safePrecision);
        return Math.floor(value * factor) / factor;
    }

    private getCommittedCapitalUsd(): number {
        return Array.from(this.executor.getAllPositions().values())
            .reduce((sum, pos) => sum + Math.abs(Number(pos.entryPrice || 0) * Number(pos.size || 0)), 0);
    }

    private async resolveAllocatedCapitalUsd(): Promise<number> {
        const profileId = this.executor.getProfileId();
        if (!profileId || profileId === 'global' || profileId.startsWith('default-')) {
            return Number(config.TOTAL_CAPITAL || 0);
        }

        const profileCapital = await supabaseService.getProfileCapital(profileId);
        if (profileCapital?.allocatedCapital && profileCapital.allocatedCapital > 0) {
            return profileCapital.allocatedCapital;
        }

        return Number(config.TOTAL_CAPITAL || 0);
    }

    private async waitForRemainingCapital(allocatedCapital: number): Promise<number> {
        const start = Date.now();

        while (Date.now() - start <= CAPITAL_WAIT_TIMEOUT_MS) {
            const committed = this.getCommittedCapitalUsd();
            const remaining = allocatedCapital - committed;
            if (remaining > 0) return remaining;
            await this.sleep(CAPITAL_WAIT_POLL_MS);
        }

        const committed = this.getCommittedCapitalUsd();
        return allocatedCapital - committed;
    }

    public async executeRequest(
        symbol: string,
        side: string,
        qty: number,
        type: string = 'market',
        price?: number,
        priceHint?: number,
        userId?: string,
        sl?: number,
        tp?: number
    ): Promise<{ success: boolean; orderId?: string; error?: string; adjustedQty?: number; requestedQty?: number; remainingCapitalUsd?: number }> {
        const signalSide = (side.toLowerCase() === 'buy') ? SignalDirection.BUY : SignalDirection.SELL;
        const requestedQty = Number(qty);
        if (!Number.isFinite(requestedQty) || requestedQty <= 0) {
            return { success: false, error: 'Invalid quantity' };
        }

        const estimatedPrice = Number(priceHint ?? price);
        if (!Number.isFinite(estimatedPrice) || estimatedPrice <= 0) {
            return { success: false, error: 'Unable to estimate price for capital guard. Retry with current price.' };
        }

        const allocatedCapital = await this.resolveAllocatedCapitalUsd();
        if (!Number.isFinite(allocatedCapital) || allocatedCapital <= 0) {
            return { success: false, error: 'Allocated capital is not configured for this profile.' };
        }

        let remainingCapital = allocatedCapital - this.getCommittedCapitalUsd();
        if (remainingCapital <= 0) {
            logger.warn(`[ManualTrader] Capital fully committed for ${this.executor.getProfileId() || this.executor.getUserId()}. Waiting for release...`);
            remainingCapital = await this.waitForRemainingCapital(allocatedCapital);
        }

        if (remainingCapital <= 0) {
            return {
                success: false,
                error: 'Capital fully committed. Waiting for capital release.',
                requestedQty,
                remainingCapitalUsd: Number(remainingCapital.toFixed(2))
            };
        }

        const precision = Math.max(0, Math.min(10, Math.floor(Number(config.QUANTITY_PRECISION || 6))));
        const minQty = Number(config.MIN_POSITION_QTY || 0.0001);
        const maxAllowedQty = this.roundDown(remainingCapital / estimatedPrice, precision);

        if (!Number.isFinite(maxAllowedQty) || maxAllowedQty < minQty) {
            return {
                success: false,
                error: `Remaining capital ${remainingCapital.toFixed(2)} is below tradable minimum.`,
                requestedQty,
                remainingCapitalUsd: Number(remainingCapital.toFixed(2))
            };
        }

        const adjustedQty = Math.min(requestedQty, maxAllowedQty);
        if (adjustedQty < requestedQty) {
            logger.warn(`[ManualTrader] Qty adjusted by capital guard for ${symbol}: requested=${requestedQty}, adjusted=${adjustedQty}, remaining=$${remainingCapital.toFixed(2)}`);
        }

        const result = await this.executor.openPosition(
            symbol,
            signalSide,
            adjustedQty,
            type as 'market' | 'limit',
            price,
            sl,
            tp,
            userId
        );

        return {
            ...result,
            adjustedQty,
            requestedQty,
            remainingCapitalUsd: Number(remainingCapital.toFixed(2))
        };
    }

    public getActivePosition(symbol: string) {
        return this.executor.getActivePosition(symbol);
    }

    public getActivePositions(symbol: string) {
        return this.executor.getActivePositions(symbol);
    }

    public async executeExit(symbol: string, currentPrice: number, reason: string, tradeId?: string) {
        return await this.executor.closePosition(symbol, reason, tradeId);
    }

    public getUserId() {
        return this.executor.getUserId();
    }

    public getProfileId() {
        return this.executor.getProfileId();
    }
}
