-- Fluxibly Monitoring Dashboard — Initial Schema
-- Run this migration to set up the monitoring tables in PostgreSQL.

-- Create monitoring schema
CREATE SCHEMA IF NOT EXISTS monitoring;

-- ═══════════════════════════════════════════════════════════
-- TRACES — top-level request grouping
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS monitoring.traces (
    trace_id        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    root_span_id    UUID,

    service_name    VARCHAR(255) NOT NULL DEFAULT 'default',
    environment     VARCHAR(50)  NOT NULL DEFAULT 'development',
    tags            JSONB        NOT NULL DEFAULT '{}',

    entry_input     TEXT,
    final_output    TEXT,
    total_duration_ms   INTEGER,
    total_cost          DECIMAL(12, 8),
    total_input_tokens  INTEGER DEFAULT 0,
    total_output_tokens INTEGER DEFAULT 0,
    total_tokens        INTEGER DEFAULT 0,
    span_count          INTEGER DEFAULT 0,

    status          VARCHAR(20)  NOT NULL DEFAULT 'in_progress',
    error_message   TEXT,

    created_at      TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ  NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_traces_created_at ON monitoring.traces (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_traces_service ON monitoring.traces (service_name, environment);
CREATE INDEX IF NOT EXISTS idx_traces_status ON monitoring.traces (status);
CREATE INDEX IF NOT EXISTS idx_traces_tags ON monitoring.traces USING GIN (tags);

-- ═══════════════════════════════════════════════════════════
-- SPANS — individual operations (Agent, LLM, Tool calls)
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS monitoring.spans (
    span_id         UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    trace_id        UUID         NOT NULL REFERENCES monitoring.traces(trace_id) ON DELETE CASCADE,
    parent_span_id  UUID         REFERENCES monitoring.spans(span_id) ON DELETE SET NULL,

    span_type       VARCHAR(20)  NOT NULL,
    component_class VARCHAR(255) NOT NULL,
    component_name  VARCHAR(255) NOT NULL,
    model           VARCHAR(255),
    provider        VARCHAR(50),

    sequence_number INTEGER      NOT NULL DEFAULT 0,

    started_at      TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    ended_at        TIMESTAMPTZ,
    duration_ms     INTEGER,

    status          VARCHAR(20)  NOT NULL DEFAULT 'in_progress',
    error_message   TEXT,

    depth           INTEGER      NOT NULL DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_spans_trace_id ON monitoring.spans (trace_id);
CREATE INDEX IF NOT EXISTS idx_spans_parent ON monitoring.spans (parent_span_id);
CREATE INDEX IF NOT EXISTS idx_spans_type ON monitoring.spans (span_type);
CREATE INDEX IF NOT EXISTS idx_spans_component_class ON monitoring.spans (component_class);
CREATE INDEX IF NOT EXISTS idx_spans_model ON monitoring.spans (model);
CREATE INDEX IF NOT EXISTS idx_spans_started_at ON monitoring.spans (started_at DESC);
CREATE INDEX IF NOT EXISTS idx_spans_provider ON monitoring.spans (provider);

-- ═══════════════════════════════════════════════════════════
-- LLM_CALL_DETAILS — detailed data for LLM spans
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS monitoring.llm_call_details (
    span_id             UUID PRIMARY KEY REFERENCES monitoring.spans(span_id) ON DELETE CASCADE,

    model               VARCHAR(255) NOT NULL,
    provider            VARCHAR(50)  NOT NULL,
    api_type            VARCHAR(20),

    input_messages      JSONB,
    input_message_count INTEGER DEFAULT 0,
    system_prompt       TEXT,

    output_text         TEXT,
    output_content      JSONB,
    reasoning_text      TEXT,
    stop_reason         VARCHAR(50),

    input_tokens        INTEGER DEFAULT 0,
    output_tokens       INTEGER DEFAULT 0,
    reasoning_tokens    INTEGER,
    cached_tokens       INTEGER,
    total_tokens        INTEGER DEFAULT 0,

    input_cost          DECIMAL(12, 8),
    output_cost         DECIMAL(12, 8),
    reasoning_cost      DECIMAL(12, 8),
    cached_cost         DECIMAL(12, 8),
    total_cost          DECIMAL(12, 8),

    temperature         FLOAT,
    max_output_tokens   INTEGER,
    top_p               FLOAT,
    reasoning_enabled   BOOLEAN DEFAULT FALSE,
    reasoning_effort    VARCHAR(10),
    streaming           BOOLEAN DEFAULT FALSE,

    response_id         VARCHAR(255),
    config_snapshot     JSONB,
    raw_response        JSONB
);

CREATE INDEX IF NOT EXISTS idx_llm_details_model ON monitoring.llm_call_details (model);
CREATE INDEX IF NOT EXISTS idx_llm_details_provider ON monitoring.llm_call_details (provider);
CREATE INDEX IF NOT EXISTS idx_llm_details_cost ON monitoring.llm_call_details (total_cost);

-- ═══════════════════════════════════════════════════════════
-- AGENT_CALL_DETAILS — detailed data for Agent spans
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS monitoring.agent_call_details (
    span_id                 UUID PRIMARY KEY REFERENCES monitoring.spans(span_id) ON DELETE CASCADE,

    agent_class             VARCHAR(255) NOT NULL,
    agent_name              VARCHAR(255) NOT NULL,

    system_prompt           TEXT,
    user_prompt_template    TEXT,

    input_messages          JSONB,
    input_message_count     INTEGER DEFAULT 0,
    output_text             TEXT,
    output_content          JSONB,
    context                 JSONB,

    tool_count              INTEGER DEFAULT 0,
    sub_agent_count         INTEGER DEFAULT 0,
    iteration_count         INTEGER DEFAULT 0,
    total_llm_calls         INTEGER DEFAULT 0,

    config_snapshot         JSONB
);

CREATE INDEX IF NOT EXISTS idx_agent_details_class ON monitoring.agent_call_details (agent_class);
CREATE INDEX IF NOT EXISTS idx_agent_details_name ON monitoring.agent_call_details (agent_name);

-- ═══════════════════════════════════════════════════════════
-- TOOL_CALL_DETAILS — each tool execution
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS monitoring.tool_call_details (
    id                  UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    span_id             UUID         NOT NULL REFERENCES monitoring.spans(span_id) ON DELETE CASCADE,

    tool_call_id        VARCHAR(255) NOT NULL,
    tool_name           VARCHAR(255) NOT NULL,
    tool_type           VARCHAR(50)  NOT NULL DEFAULT 'function',

    arguments           JSONB,
    result_content      TEXT,
    is_error            BOOLEAN      NOT NULL DEFAULT FALSE,

    started_at          TIMESTAMPTZ,
    ended_at            TIMESTAMPTZ,
    duration_ms         INTEGER,

    iteration_number    INTEGER      NOT NULL DEFAULT 1,
    sequence_in_batch   INTEGER      NOT NULL DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_tool_details_span ON monitoring.tool_call_details (span_id);
CREATE INDEX IF NOT EXISTS idx_tool_details_name ON monitoring.tool_call_details (tool_name);
CREATE INDEX IF NOT EXISTS idx_tool_details_type ON monitoring.tool_call_details (tool_type);

-- ═══════════════════════════════════════════════════════════
-- USAGE_HOURLY_ROLLUP — pre-aggregated usage stats
-- ═══════════════════════════════════════════════════════════
CREATE TABLE IF NOT EXISTS monitoring.usage_hourly_rollup (
    id                  BIGSERIAL PRIMARY KEY,
    hour_bucket         TIMESTAMPTZ  NOT NULL,

    component_type      VARCHAR(20)  NOT NULL,
    component_class     VARCHAR(255) NOT NULL,
    component_name      VARCHAR(255) NOT NULL,
    model               VARCHAR(255),
    provider            VARCHAR(50),
    service_name        VARCHAR(255) NOT NULL DEFAULT 'default',
    environment         VARCHAR(50)  NOT NULL DEFAULT 'development',

    request_count       INTEGER      NOT NULL DEFAULT 0,
    total_input_tokens  BIGINT       NOT NULL DEFAULT 0,
    total_output_tokens BIGINT       NOT NULL DEFAULT 0,
    total_reasoning_tokens BIGINT    DEFAULT 0,
    total_tokens        BIGINT       NOT NULL DEFAULT 0,
    total_cost          DECIMAL(12, 8) DEFAULT 0,
    avg_duration_ms     FLOAT,
    min_duration_ms     INTEGER,
    max_duration_ms     INTEGER,
    error_count         INTEGER      NOT NULL DEFAULT 0,

    UNIQUE (hour_bucket, component_type, component_class, component_name, model, service_name, environment)
);

CREATE INDEX IF NOT EXISTS idx_rollup_hour ON monitoring.usage_hourly_rollup (hour_bucket DESC);
CREATE INDEX IF NOT EXISTS idx_rollup_component ON monitoring.usage_hourly_rollup (component_type, component_class);
CREATE INDEX IF NOT EXISTS idx_rollup_model ON monitoring.usage_hourly_rollup (model);
CREATE INDEX IF NOT EXISTS idx_rollup_service ON monitoring.usage_hourly_rollup (service_name, environment);

-- ═══════════════════════════════════════════════════════════
-- Foreign key from traces to root span (deferred)
-- ═══════════════════════════════════════════════════════════
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_constraint WHERE conname = 'fk_traces_root_span'
    ) THEN
        ALTER TABLE monitoring.traces
            ADD CONSTRAINT fk_traces_root_span
            FOREIGN KEY (root_span_id) REFERENCES monitoring.spans(span_id)
            ON DELETE SET NULL
            DEFERRABLE INITIALLY DEFERRED;
    END IF;
END $$;
