-- ============================================================================
-- SCHEMA: trading
-- TABLE: positions
-- DESCRIPTION: Posiciones de trading (abiertas y cerradas)
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 3 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Tabla de Posiciones de Trading
CREATE TABLE IF NOT EXISTS trading.positions (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,

    -- Referencias
    bot_id UUID REFERENCES trading.bots(id),
    strategy_id UUID REFERENCES trading.strategies(id),
    signal_id UUID REFERENCES trading.signals(id),
    symbol_id UUID REFERENCES trading.symbols(id),

    -- Identificadores externos
    external_ticket VARCHAR(50),                    -- Ticket del broker/MT4
    external_order_id VARCHAR(50),

    -- Instrumento
    symbol VARCHAR(20) NOT NULL,

    -- Direccion y estado
    direction trading.trade_direction NOT NULL,
    status trading.position_status NOT NULL DEFAULT 'pending',
    order_type trading.order_type NOT NULL DEFAULT 'market',

    -- Tamaño
    lot_size DECIMAL(10, 4) NOT NULL,
    units INTEGER,                                  -- Unidades/cantidad

    -- Precios de entrada
    requested_price DECIMAL(15, 8),
    entry_price DECIMAL(15, 8),
    slippage DECIMAL(15, 8),

    -- Stop Loss
    stop_loss DECIMAL(15, 8),
    stop_loss_pips DECIMAL(10, 4),
    original_stop_loss DECIMAL(15, 8),
    trailing_stop_enabled BOOLEAN NOT NULL DEFAULT FALSE,
    trailing_stop_distance DECIMAL(10, 4),

    -- Take Profit
    take_profit DECIMAL(15, 8),
    take_profit_pips DECIMAL(10, 4),
    take_profit_2 DECIMAL(15, 8),
    take_profit_3 DECIMAL(15, 8),

    -- Breakeven
    breakeven_activated BOOLEAN NOT NULL DEFAULT FALSE,
    breakeven_trigger_pips DECIMAL(10, 4),
    breakeven_offset_pips DECIMAL(10, 4),

    -- Precios de cierre
    exit_price DECIMAL(15, 8),
    exit_reason VARCHAR(50),                        -- 'manual', 'stop_loss', 'take_profit', 'trailing_stop', 'margin_call'

    -- Profit/Loss
    profit_loss DECIMAL(15, 4),                     -- P&L en moneda de cuenta
    profit_loss_pips DECIMAL(10, 4),
    profit_loss_percent DECIMAL(10, 4),

    -- Comisiones y costos
    commission DECIMAL(15, 4) DEFAULT 0,
    swap DECIMAL(15, 4) DEFAULT 0,                  -- Swap overnight
    spread_cost DECIMAL(15, 4) DEFAULT 0,
    total_cost DECIMAL(15, 4) DEFAULT 0,
    net_profit DECIMAL(15, 4),                      -- P&L - costos

    -- Margen
    margin_used DECIMAL(15, 4),
    leverage_used INTEGER,

    -- Risk management
    risk_amount DECIMAL(15, 4),                     -- Monto en riesgo
    risk_percent DECIMAL(5, 2),                     -- % de cuenta en riesgo
    reward_amount DECIMAL(15, 4),
    risk_reward_ratio DECIMAL(10, 4),

    -- Tiempo
    duration_seconds INTEGER,
    duration_formatted VARCHAR(50),                 -- "2h 30m"

    -- Partial closes
    partial_closes JSONB DEFAULT '[]'::JSONB,      -- [{ "lot_size": 0.05, "price": 1.1234, "pnl": 50, "at": "..." }]
    remaining_lot_size DECIMAL(10, 4),

    -- Escalado
    scale_ins JSONB DEFAULT '[]'::JSONB,           -- Adiciones a la posicion
    average_entry_price DECIMAL(15, 8),

    -- Precio actual (para posiciones abiertas)
    current_price DECIMAL(15, 8),
    unrealized_pnl DECIMAL(15, 4),
    unrealized_pnl_pips DECIMAL(10, 4),
    price_updated_at TIMESTAMPTZ,

    -- Analisis post-trade
    max_favorable_excursion DECIMAL(15, 4),        -- MFE - max ganancia no realizada
    max_adverse_excursion DECIMAL(15, 4),          -- MAE - max perdida no realizada
    mfe_price DECIMAL(15, 8),
    mae_price DECIMAL(15, 8),

    -- Screenshots/Evidence
    entry_screenshot_url TEXT,
    exit_screenshot_url TEXT,

    -- Notas
    notes TEXT,
    tags VARCHAR(50)[],

    -- Metadata
    metadata JSONB DEFAULT '{}'::JSONB,

    -- Timestamps
    requested_at TIMESTAMPTZ,
    opened_at TIMESTAMPTZ,
    closed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE trading.positions IS
'Posiciones de trading con tracking completo de entrada, salida y P&L';

COMMENT ON COLUMN trading.positions.max_favorable_excursion IS
'Maximum Favorable Excursion - maxima ganancia no realizada durante el trade';

-- Indices
CREATE INDEX IF NOT EXISTS idx_positions_tenant
    ON trading.positions(tenant_id);

CREATE INDEX IF NOT EXISTS idx_positions_user
    ON trading.positions(user_id);

CREATE INDEX IF NOT EXISTS idx_positions_bot
    ON trading.positions(bot_id)
    WHERE bot_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_positions_strategy
    ON trading.positions(strategy_id)
    WHERE strategy_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_positions_signal
    ON trading.positions(signal_id)
    WHERE signal_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_positions_symbol
    ON trading.positions(symbol);

CREATE INDEX IF NOT EXISTS idx_positions_status
    ON trading.positions(status);

CREATE INDEX IF NOT EXISTS idx_positions_open
    ON trading.positions(tenant_id, user_id, status)
    WHERE status = 'open';

CREATE INDEX IF NOT EXISTS idx_positions_closed
    ON trading.positions(tenant_id, closed_at DESC)
    WHERE status IN ('closed', 'stopped', 'target_hit');

CREATE INDEX IF NOT EXISTS idx_positions_external
    ON trading.positions(external_ticket)
    WHERE external_ticket IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_positions_pnl
    ON trading.positions(profit_loss DESC)
    WHERE status IN ('closed', 'stopped', 'target_hit');

-- Trigger para updated_at
DROP TRIGGER IF EXISTS position_updated_at ON trading.positions;
CREATE TRIGGER position_updated_at
    BEFORE UPDATE ON trading.positions
    FOR EACH ROW
    EXECUTE FUNCTION trading.update_trading_timestamp();

-- Trigger para calcular campos al abrir posicion
CREATE OR REPLACE FUNCTION trading.calculate_position_on_open()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.status = 'open' AND OLD.status = 'pending' THEN
        NEW.opened_at := NOW();

        -- Calcular slippage
        IF NEW.requested_price IS NOT NULL AND NEW.entry_price IS NOT NULL THEN
            NEW.slippage := ABS(NEW.entry_price - NEW.requested_price);
        END IF;

        -- Calcular pips de SL/TP
        IF NEW.stop_loss IS NOT NULL AND NEW.entry_price IS NOT NULL THEN
            IF NEW.direction = 'long' THEN
                NEW.stop_loss_pips := (NEW.entry_price - NEW.stop_loss) * 10000;
            ELSE
                NEW.stop_loss_pips := (NEW.stop_loss - NEW.entry_price) * 10000;
            END IF;
        END IF;

        IF NEW.take_profit IS NOT NULL AND NEW.entry_price IS NOT NULL THEN
            IF NEW.direction = 'long' THEN
                NEW.take_profit_pips := (NEW.take_profit - NEW.entry_price) * 10000;
            ELSE
                NEW.take_profit_pips := (NEW.entry_price - NEW.take_profit) * 10000;
            END IF;
        END IF;

        -- Calcular risk/reward ratio
        IF NEW.stop_loss_pips > 0 AND NEW.take_profit_pips IS NOT NULL THEN
            NEW.risk_reward_ratio := NEW.take_profit_pips / NEW.stop_loss_pips;
        END IF;

        NEW.remaining_lot_size := NEW.lot_size;
        NEW.average_entry_price := NEW.entry_price;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS position_on_open ON trading.positions;
CREATE TRIGGER position_on_open
    BEFORE UPDATE OF status ON trading.positions
    FOR EACH ROW
    EXECUTE FUNCTION trading.calculate_position_on_open();

-- Trigger para calcular campos al cerrar posicion
CREATE OR REPLACE FUNCTION trading.calculate_position_on_close()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.status IN ('closed', 'stopped', 'target_hit') AND OLD.status = 'open' THEN
        NEW.closed_at := NOW();

        -- Calcular P&L en pips
        IF NEW.exit_price IS NOT NULL AND NEW.entry_price IS NOT NULL THEN
            IF NEW.direction = 'long' THEN
                NEW.profit_loss_pips := (NEW.exit_price - NEW.entry_price) * 10000;
            ELSE
                NEW.profit_loss_pips := (NEW.entry_price - NEW.exit_price) * 10000;
            END IF;
        END IF;

        -- Calcular duracion
        IF NEW.opened_at IS NOT NULL THEN
            NEW.duration_seconds := EXTRACT(EPOCH FROM (NEW.closed_at - NEW.opened_at))::INTEGER;

            -- Formatear duracion
            NEW.duration_formatted :=
                CASE
                    WHEN NEW.duration_seconds < 60 THEN NEW.duration_seconds || 's'
                    WHEN NEW.duration_seconds < 3600 THEN (NEW.duration_seconds / 60) || 'm'
                    WHEN NEW.duration_seconds < 86400 THEN (NEW.duration_seconds / 3600) || 'h ' || ((NEW.duration_seconds % 3600) / 60) || 'm'
                    ELSE (NEW.duration_seconds / 86400) || 'd ' || ((NEW.duration_seconds % 86400) / 3600) || 'h'
                END;
        END IF;

        -- Calcular costos totales
        NEW.total_cost := COALESCE(NEW.commission, 0) + COALESCE(NEW.swap, 0) + COALESCE(NEW.spread_cost, 0);
        NEW.net_profit := COALESCE(NEW.profit_loss, 0) - NEW.total_cost;

        -- Determinar exit_reason basado en status
        IF NEW.exit_reason IS NULL THEN
            NEW.exit_reason := CASE NEW.status
                WHEN 'stopped' THEN 'stop_loss'
                WHEN 'target_hit' THEN 'take_profit'
                ELSE 'manual'
            END;
        END IF;

        -- Actualizar senal si existe
        IF NEW.signal_id IS NOT NULL THEN
            UPDATE trading.signals
            SET status = 'executed',
                executed_at = NEW.closed_at,
                executed_price = NEW.exit_price,
                result = CASE
                    WHEN NEW.profit_loss > 0 THEN 'win'
                    WHEN NEW.profit_loss < 0 THEN 'loss'
                    ELSE 'breakeven'
                END,
                result_pips = NEW.profit_loss_pips,
                result_amount = NEW.profit_loss,
                position_id = NEW.id
            WHERE id = NEW.signal_id;
        END IF;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS position_on_close ON trading.positions;
CREATE TRIGGER position_on_close
    BEFORE UPDATE OF status ON trading.positions
    FOR EACH ROW
    EXECUTE FUNCTION trading.calculate_position_on_close();

-- Trigger para actualizar estadisticas de bot/strategy
CREATE OR REPLACE FUNCTION trading.update_stats_on_position_close()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.status IN ('closed', 'stopped', 'target_hit') AND OLD.status = 'open' THEN
        -- Actualizar bot stats
        IF NEW.bot_id IS NOT NULL THEN
            PERFORM trading.recalculate_bot_stats(NEW.bot_id);
        END IF;

        -- Actualizar strategy stats
        IF NEW.strategy_id IS NOT NULL THEN
            PERFORM trading.recalculate_strategy_stats(NEW.strategy_id);
        END IF;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS position_stats_update ON trading.positions;
CREATE TRIGGER position_stats_update
    AFTER UPDATE OF status ON trading.positions
    FOR EACH ROW
    EXECUTE FUNCTION trading.update_stats_on_position_close();

-- Vista de posiciones abiertas
CREATE OR REPLACE VIEW trading.v_open_positions AS
SELECT
    p.id,
    p.tenant_id,
    p.user_id,
    p.bot_id,
    b.name AS bot_name,
    p.symbol,
    p.direction,
    p.lot_size,
    p.entry_price,
    p.stop_loss,
    p.take_profit,
    p.current_price,
    p.unrealized_pnl,
    p.unrealized_pnl_pips,
    p.risk_reward_ratio,
    p.opened_at,
    NOW() - p.opened_at AS time_open
FROM trading.positions p
LEFT JOIN trading.bots b ON p.bot_id = b.id
WHERE p.status = 'open'
ORDER BY p.opened_at DESC;

-- Vista de historial de trades
CREATE OR REPLACE VIEW trading.v_trade_history AS
SELECT
    p.id,
    p.tenant_id,
    p.user_id,
    p.symbol,
    p.direction,
    p.lot_size,
    p.entry_price,
    p.exit_price,
    p.stop_loss,
    p.take_profit,
    p.profit_loss,
    p.profit_loss_pips,
    p.net_profit,
    p.exit_reason,
    p.duration_formatted,
    p.opened_at,
    p.closed_at
FROM trading.positions p
WHERE p.status IN ('closed', 'stopped', 'target_hit')
ORDER BY p.closed_at DESC;

-- RLS Policy para multi-tenancy
ALTER TABLE trading.positions ENABLE ROW LEVEL SECURITY;

CREATE POLICY positions_tenant_isolation ON trading.positions
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Los usuarios solo ven sus propias posiciones
CREATE POLICY positions_user_isolation ON trading.positions
    FOR SELECT
    USING (user_id = current_setting('app.current_user_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE ON trading.positions TO trading_app;
GRANT SELECT ON trading.positions TO trading_readonly;
GRANT SELECT ON trading.v_open_positions TO trading_app;
GRANT SELECT ON trading.v_trade_history TO trading_app;
