-- Migration 001: Create core trading tables for HFT system
-- This migration establishes the foundational tables for orders, fills, positions, and market data

-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements";

-- Orders table - core trading orders with optimized indexing
CREATE TABLE IF NOT EXISTS orders (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    symbol VARCHAR(32) NOT NULL,
    side VARCHAR(10) NOT NULL CHECK (side IN ('buy', 'sell')),
    order_type VARCHAR(20) NOT NULL CHECK (order_type IN ('market', 'limit', 'stop', 'stop_limit')),
    quantity BIGINT NOT NULL CHECK (quantity > 0),
    price BIGINT, -- Fixed-point price in cents, nullable for market orders
    filled_quantity BIGINT NOT NULL DEFAULT 0 CHECK (filled_quantity >= 0),
    status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'partial', 'filled', 'cancelled', 'expired')),
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMP WITH TIME ZONE,
    client_order_id VARCHAR(128), -- Client-provided identifier
    account_id VARCHAR(64), -- Account identifier
    metadata JSONB -- Additional order metadata
);

-- Fills table - trade executions with foreign key to orders
CREATE TABLE IF NOT EXISTS fills (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    symbol VARCHAR(32) NOT NULL,
    side VARCHAR(10) NOT NULL CHECK (side IN ('buy', 'sell')),
    quantity BIGINT NOT NULL CHECK (quantity > 0),
    price BIGINT NOT NULL CHECK (price > 0), -- Execution price in fixed-point cents
    fee BIGINT, -- Trading fee in fixed-point cents
    fee_currency VARCHAR(10), -- Fee currency
    execution_time TIMESTAMP WITH TIME ZONE NOT NULL,
    venue VARCHAR(64), -- Execution venue
    execution_id VARCHAR(128), -- Venue-specific execution ID
    is_maker BOOLEAN, -- Maker/taker classification
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    metadata JSONB -- Additional fill metadata
);

-- Positions table - current holdings by symbol and account
CREATE TABLE IF NOT EXISTS positions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    symbol VARCHAR(32) NOT NULL,
    account_id VARCHAR(64), -- Account identifier
    quantity BIGINT NOT NULL DEFAULT 0, -- Signed quantity (positive = long, negative = short)
    avg_price BIGINT NOT NULL DEFAULT 0, -- Average entry price in fixed-point cents
    market_value BIGINT NOT NULL DEFAULT 0, -- Current market value in fixed-point cents
    unrealized_pnl BIGINT NOT NULL DEFAULT 0, -- Unrealized P&L in fixed-point cents
    realized_pnl BIGINT NOT NULL DEFAULT 0, -- Realized P&L in fixed-point cents
    total_cost BIGINT NOT NULL DEFAULT 0, -- Total cost basis in fixed-point cents
    last_price BIGINT NOT NULL DEFAULT 0, -- Last known market price
    trade_count INTEGER NOT NULL DEFAULT 0, -- Number of trades that created this position
    first_trade_time TIMESTAMP WITH TIME ZONE, -- Time of first trade
    last_updated TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    metadata JSONB, -- Additional position metadata
    
    -- Ensure unique position per symbol-account combination
    UNIQUE(symbol, account_id)
);

-- Market data table - high-frequency tick data with partitioning support
CREATE TABLE IF NOT EXISTS market_data (
    id UUID NOT NULL DEFAULT uuid_generate_v4(),
    symbol VARCHAR(32) NOT NULL,
    timestamp TIMESTAMP WITH TIME ZONE NOT NULL, -- Market timestamp
    received_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), -- When we received the data
    bid BIGINT, -- Best bid price in fixed-point cents
    ask BIGINT, -- Best ask price in fixed-point cents
    last BIGINT, -- Last trade price in fixed-point cents
    volume BIGINT, -- Volume
    bid_size BIGINT, -- Best bid size
    ask_size BIGINT, -- Best ask size
    trade_count INTEGER, -- Number of trades
    vwap BIGINT, -- Volume weighted average price
    open BIGINT, -- Opening price
    high BIGINT, -- High price
    low BIGINT, -- Low price
    close BIGINT, -- Closing price
    data_type VARCHAR(20) NOT NULL DEFAULT 'tick' CHECK (data_type IN ('tick', 'quote', 'trade', 'bar')),
    source VARCHAR(64) NOT NULL, -- Data provider
    metadata JSONB, -- Additional market data
    
    PRIMARY KEY (id, timestamp) -- Composite primary key for partitioning
) PARTITION BY RANGE (timestamp);

-- Create initial partition for market data (current month)
DO $$
DECLARE
    partition_start DATE := date_trunc('month', CURRENT_DATE);
    partition_end DATE := partition_start + INTERVAL '1 month';
    partition_name TEXT := 'market_data_' || to_char(partition_start, 'YYYY_MM');
BEGIN
    EXECUTE format('CREATE TABLE IF NOT EXISTS %I PARTITION OF market_data 
                   FOR VALUES FROM (%L) TO (%L)', 
                   partition_name, partition_start, partition_end);
END $$;

-- Bars table - aggregated OHLCV data
CREATE TABLE IF NOT EXISTS bars (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    symbol VARCHAR(32) NOT NULL,
    timeframe VARCHAR(10) NOT NULL, -- "1m", "5m", "1h", "1d", etc.
    timestamp TIMESTAMP WITH TIME ZONE NOT NULL, -- Bar start time
    open BIGINT NOT NULL, -- Opening price in fixed-point cents
    high BIGINT NOT NULL, -- High price in fixed-point cents
    low BIGINT NOT NULL, -- Low price in fixed-point cents
    close BIGINT NOT NULL, -- Closing price in fixed-point cents
    volume BIGINT NOT NULL DEFAULT 0, -- Volume
    trade_count INTEGER NOT NULL DEFAULT 0, -- Number of trades
    vwap BIGINT, -- Volume weighted average price
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    
    -- Ensure unique bar per symbol-timeframe-timestamp combination
    UNIQUE(symbol, timeframe, timestamp)
);

-- Create high-performance indexes for HFT queries

-- Orders table indexes (optimized for order management)
CREATE INDEX IF NOT EXISTS idx_orders_symbol_status ON orders(symbol, status);
CREATE INDEX IF NOT EXISTS idx_orders_created_at ON orders(created_at);
CREATE INDEX IF NOT EXISTS idx_orders_account_id ON orders(account_id) WHERE account_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_orders_client_order_id ON orders(client_order_id) WHERE client_order_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_orders_symbol_status_created ON orders(symbol, status, created_at);

-- Fills table indexes (optimized for execution tracking)
CREATE INDEX IF NOT EXISTS idx_fills_order_id ON fills(order_id);
CREATE INDEX IF NOT EXISTS idx_fills_symbol_execution_time ON fills(symbol, execution_time);
CREATE INDEX IF NOT EXISTS idx_fills_execution_time ON fills(execution_time);
CREATE INDEX IF NOT EXISTS idx_fills_venue ON fills(venue) WHERE venue IS NOT NULL;

-- Positions table indexes (optimized for position tracking)
CREATE INDEX IF NOT EXISTS idx_positions_symbol ON positions(symbol);
CREATE INDEX IF NOT EXISTS idx_positions_account_id ON positions(account_id) WHERE account_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_positions_last_updated ON positions(last_updated);

-- Market data table indexes (optimized for time-series queries)
CREATE INDEX IF NOT EXISTS idx_market_data_symbol_timestamp ON market_data(symbol, timestamp);
CREATE INDEX IF NOT EXISTS idx_market_data_timestamp ON market_data(timestamp);
CREATE INDEX IF NOT EXISTS idx_market_data_source ON market_data(source);
CREATE INDEX IF NOT EXISTS idx_market_data_received_at ON market_data(received_at);

-- Bars table indexes (optimized for chart data queries)
CREATE INDEX IF NOT EXISTS idx_bars_symbol_timeframe_timestamp ON bars(symbol, timeframe, timestamp);
CREATE INDEX IF NOT EXISTS idx_bars_timestamp ON bars(timestamp);

-- Create functions for automatic timestamp updates
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Create triggers for automatic timestamp updates
CREATE TRIGGER trigger_orders_updated_at
    BEFORE UPDATE ON orders
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

CREATE TRIGGER trigger_positions_updated_at
    BEFORE UPDATE ON positions
    FOR EACH ROW
    EXECUTE FUNCTION update_updated_at_column();

-- Create function to automatically create market data partitions
CREATE OR REPLACE FUNCTION create_market_data_partition_if_not_exists(target_date DATE)
RETURNS VOID AS $$
DECLARE
    partition_start DATE := date_trunc('month', target_date);
    partition_end DATE := partition_start + INTERVAL '1 month';
    partition_name TEXT := 'market_data_' || to_char(partition_start, 'YYYY_MM');
BEGIN
    -- Check if partition exists
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.tables 
        WHERE table_name = partition_name
    ) THEN
        EXECUTE format('CREATE TABLE %I PARTITION OF market_data 
                       FOR VALUES FROM (%L) TO (%L)', 
                       partition_name, partition_start, partition_end);
        
        -- Add indexes to the new partition
        EXECUTE format('CREATE INDEX %I ON %I(symbol, timestamp)', 
                       'idx_' || partition_name || '_symbol_timestamp', partition_name);
        EXECUTE format('CREATE INDEX %I ON %I(timestamp)', 
                       'idx_' || partition_name || '_timestamp', partition_name);
    END IF;
END;
$$ LANGUAGE plpgsql;

-- Create function to validate order constraints
CREATE OR REPLACE FUNCTION validate_order_constraints()
RETURNS TRIGGER AS $$
BEGIN
    -- Validate that limit orders have a price
    IF NEW.order_type = 'limit' AND NEW.price IS NULL THEN
        RAISE EXCEPTION 'Limit orders must have a price';
    END IF;
    
    -- Validate that filled quantity doesn't exceed order quantity
    IF NEW.filled_quantity > NEW.quantity THEN
        RAISE EXCEPTION 'Filled quantity cannot exceed order quantity';
    END IF;
    
    -- Update status based on filled quantity
    IF NEW.filled_quantity = 0 THEN
        NEW.status = 'pending';
    ELSIF NEW.filled_quantity = NEW.quantity THEN
        NEW.status = 'filled';
    ELSIF NEW.filled_quantity < NEW.quantity THEN
        NEW.status = 'partial';
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Create trigger for order validation
CREATE TRIGGER trigger_validate_orders
    BEFORE INSERT OR UPDATE ON orders
    FOR EACH ROW
    EXECUTE FUNCTION validate_order_constraints();

-- Create materialized view for fast position summaries
CREATE MATERIALIZED VIEW IF NOT EXISTS position_summaries AS
SELECT 
    symbol,
    account_id,
    SUM(quantity) as total_quantity,
    COUNT(*) as position_count,
    SUM(unrealized_pnl) as total_unrealized_pnl,
    SUM(realized_pnl) as total_realized_pnl,
    AVG(avg_price) as weighted_avg_price,
    MAX(last_updated) as last_updated
FROM positions 
WHERE quantity != 0
GROUP BY symbol, account_id;

-- Create unique index on the materialized view
CREATE UNIQUE INDEX IF NOT EXISTS idx_position_summaries_symbol_account 
ON position_summaries(symbol, account_id);

-- Create function to refresh position summaries
CREATE OR REPLACE FUNCTION refresh_position_summaries()
RETURNS VOID AS $$
BEGIN
    REFRESH MATERIALIZED VIEW CONCURRENTLY position_summaries;
END;
$$ LANGUAGE plpgsql;

-- Add comments for documentation
COMMENT ON TABLE orders IS 'Core trading orders with ACID compliance';
COMMENT ON TABLE fills IS 'Trade executions linked to orders';
COMMENT ON TABLE positions IS 'Current holdings by symbol and account';
COMMENT ON TABLE market_data IS 'High-frequency tick data with automatic partitioning';
COMMENT ON TABLE bars IS 'Aggregated OHLCV bars for charting';

COMMENT ON COLUMN orders.price IS 'Price in fixed-point cents (divide by 100 for dollars)';
COMMENT ON COLUMN orders.quantity IS 'Order quantity in shares/units';
COMMENT ON COLUMN fills.price IS 'Execution price in fixed-point cents';
COMMENT ON COLUMN positions.quantity IS 'Signed quantity: positive=long, negative=short';

-- Grant appropriate permissions (adjust as needed for your setup)
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO foxhunt_app;
-- GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO foxhunt_app;