-- ============================================================
-- Migration 012: Atomic ENTRY Lifecycle Persistence
-- Date: 2026-02-16
-- Purpose:
--   1) Add explicit trade_lifecycle and positions tables for traceability.
--   2) Harden orders/trade_history uniqueness to drive idempotent RPC inserts.
--   3) Expose fn_persist_entry_lifecycle for exchange-first persistence.
-- ============================================================

BEGIN;

-- --------------------------------------------------------------------------------
-- Ensure lifecycle table captures profile + trade scope
-- --------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS trade_lifecycle (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id uuid NOT NULL REFERENCES trade_profiles(id) ON DELETE CASCADE,
    trade_id text NOT NULL,
    entry_order_id text NOT NULL,
    current_stage text NOT NULL DEFAULT 'ENTRY',
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    CONSTRAINT trade_lifecycle_profile_trade_unique UNIQUE (profile_id, trade_id)
);

CREATE INDEX IF NOT EXISTS idx_trade_lifecycle_entry_order
    ON trade_lifecycle(entry_order_id);

-- --------------------------------------------------------------------------------
-- Positions table for lifecycle-driven snapshots
-- --------------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS positions (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id uuid NOT NULL,
    trade_id text NOT NULL,
    entry_order_id text NOT NULL,
    symbol text NOT NULL,
    side text NOT NULL,
    quantity numeric NOT NULL DEFAULT 0,
    avg_price numeric NOT NULL DEFAULT 0,
    status text NOT NULL DEFAULT 'OPEN',
    created_at timestamptz NOT NULL DEFAULT now(),
    updated_at timestamptz NOT NULL DEFAULT now(),
    CONSTRAINT positions_profile_trade_unique UNIQUE (profile_id, trade_id),
    CONSTRAINT positions_lifecycle_fk FOREIGN KEY (profile_id, trade_id)
        REFERENCES trade_lifecycle(profile_id, trade_id) ON DELETE CASCADE,
    CONSTRAINT chk_positions_side CHECK (side IN ('BUY','SELL'))
);

CREATE INDEX IF NOT EXISTS idx_positions_profile_trade
    ON positions (profile_id, trade_id);
CREATE INDEX IF NOT EXISTS idx_positions_symbol ON positions (symbol);

-- --------------------------------------------------------------------------------
-- Harden orders + trade_history uniqueness for idempotent child inserts
-- --------------------------------------------------------------------------------
CREATE UNIQUE INDEX IF NOT EXISTS uq_orders_order_id ON orders (order_id) WHERE order_id IS NOT NULL;

ALTER TABLE IF EXISTS trade_history
    ADD COLUMN IF NOT EXISTS lifecycle_marker text;

CREATE UNIQUE INDEX IF NOT EXISTS uq_trade_history_lifecycle_marker
    ON trade_history (lifecycle_marker)
    WHERE lifecycle_marker IS NOT NULL;

COMMIT;

-- --------------------------------------------------------------------------------
-- Entry persistence RPC
-- --------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION fn_persist_entry_lifecycle(
    p_profile_id uuid,
    p_trade_id text,
    p_order_id text,
    p_symbol text,
    p_side text,
    p_type text,
    p_quantity numeric,
    p_price numeric,
    p_status text,
    p_timestamp bigint,
    p_stop_loss numeric DEFAULT NULL,
    p_take_profit numeric DEFAULT NULL,
    p_source text DEFAULT 'BOT',
    p_reason text DEFAULT 'Entry lifecycle persisted'
) RETURNS jsonb AS $$
DECLARE
    lifecycle_row trade_lifecycle%ROWTYPE;
    entry_marker text := format('entry:%s', p_trade_id);
BEGIN
    BEGIN
        INSERT INTO trade_lifecycle (
            profile_id,
            trade_id,
            entry_order_id,
            current_stage,
            created_at,
            updated_at
        ) VALUES (
            p_profile_id,
            p_trade_id,
            p_order_id,
            'ENTRY',
            now(),
            now()
        )
        RETURNING * INTO lifecycle_row;
    EXCEPTION WHEN unique_violation THEN
        UPDATE trade_lifecycle
        SET updated_at = now()
        WHERE profile_id = p_profile_id
          AND trade_id = p_trade_id;
    END;

    INSERT INTO orders (
        order_id,
        profile_id,
        trade_id,
        symbol,
        type,
        side,
        qty,
        quantity,
        price,
        status,
        timestamp,
        stop_loss,
        take_profit,
        action,
        source
    ) VALUES (
        p_order_id,
        p_profile_id,
        p_trade_id,
        p_symbol,
        p_type,
        upper(p_side),
        COALESCE(p_quantity, 0),
        COALESCE(p_quantity, 0),
        COALESCE(p_price, 0),
        p_status,
        p_timestamp,
        p_stop_loss,
        p_take_profit,
        'ENTRY',
        p_source
    ) ON CONFLICT (order_id) DO NOTHING;

    INSERT INTO positions (
        profile_id,
        trade_id,
        entry_order_id,
        symbol,
        side,
        quantity,
        avg_price,
        status,
        created_at,
        updated_at
    ) VALUES (
        p_profile_id,
        p_trade_id,
        p_order_id,
        p_symbol,
        upper(p_side),
        0,
        COALESCE(p_price, 0),
        'OPEN',
        now(),
        now()
    ) ON CONFLICT (profile_id, trade_id) DO NOTHING;

    INSERT INTO trade_history (
        trade_id,
        profile_id,
        symbol,
        side,
        size,
        entry_price,
        exit_price,
        pnl,
        pnl_percent,
        reason,
        timestamp,
        stop_loss,
        take_profit,
        source,
        lifecycle_marker
    ) VALUES (
        p_trade_id,
        p_profile_id,
        p_symbol,
        upper(p_side),
        COALESCE(p_quantity, 0),
        COALESCE(p_price, 0),
        NULL,
        0,
        0,
        p_reason,
        p_timestamp,
        p_stop_loss,
        p_take_profit,
        p_source,
        entry_marker
    ) ON CONFLICT (lifecycle_marker) DO NOTHING;

    IF lifecycle_row IS NULL THEN
        SELECT * INTO lifecycle_row
        FROM trade_lifecycle
        WHERE profile_id = p_profile_id
          AND trade_id = p_trade_id;
    END IF;

    RETURN row_to_json(lifecycle_row);
END;
$$ LANGUAGE plpgsql;
