-- ================================================================================================
-- Migration 046: Batch Job Tracking Schema
-- Support for batch job orchestration with parent-child job hierarchy and progress aggregation
-- ================================================================================================

-- ================================================================================================
-- BATCH JOBS TABLE
-- Parent jobs that orchestrate multiple child training jobs
-- ================================================================================================
CREATE TABLE IF NOT EXISTS batch_jobs (
    -- Primary identifiers
    id UUID PRIMARY KEY,
    
    -- Batch job metadata
    name VARCHAR(255) NOT NULL,
    description TEXT,
    
    -- Status tracking
    status VARCHAR(50) NOT NULL DEFAULT 'Pending',
    
    -- Progress aggregation
    total_jobs INTEGER NOT NULL DEFAULT 0,
    pending_jobs INTEGER NOT NULL DEFAULT 0,
    running_jobs INTEGER NOT NULL DEFAULT 0,
    completed_jobs INTEGER NOT NULL DEFAULT 0,
    failed_jobs INTEGER NOT NULL DEFAULT 0,
    overall_progress DOUBLE PRECISION NOT NULL DEFAULT 0.0,
    
    -- Configuration
    config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
    
    -- Audit timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    
    -- Constraints
    CONSTRAINT chk_batch_job_status CHECK (
        status IN ('Pending', 'Running', 'Completed', 'Failed', 'Stopped', 'Paused')
    ),
    CONSTRAINT chk_batch_job_counts CHECK (
        total_jobs >= 0 AND
        pending_jobs >= 0 AND
        running_jobs >= 0 AND
        completed_jobs >= 0 AND
        failed_jobs >= 0 AND
        (pending_jobs + running_jobs + completed_jobs + failed_jobs) <= total_jobs
    ),
    CONSTRAINT chk_batch_job_progress CHECK (
        overall_progress >= 0.0 AND overall_progress <= 100.0
    )
);

-- Add table comment
COMMENT ON TABLE batch_jobs IS 'Parent batch jobs that orchestrate multiple child training jobs with progress aggregation';

-- Add column comments
COMMENT ON COLUMN batch_jobs.id IS 'Unique batch job identifier';
COMMENT ON COLUMN batch_jobs.name IS 'Human-readable batch job name';
COMMENT ON COLUMN batch_jobs.status IS 'Current batch job status (Pending, Running, Completed, Failed, Stopped, Paused)';
COMMENT ON COLUMN batch_jobs.total_jobs IS 'Total number of child jobs in this batch';
COMMENT ON COLUMN batch_jobs.pending_jobs IS 'Number of pending child jobs';
COMMENT ON COLUMN batch_jobs.running_jobs IS 'Number of running child jobs';
COMMENT ON COLUMN batch_jobs.completed_jobs IS 'Number of completed child jobs';
COMMENT ON COLUMN batch_jobs.failed_jobs IS 'Number of failed child jobs';
COMMENT ON COLUMN batch_jobs.overall_progress IS 'Weighted overall progress (0.0-100.0)';
COMMENT ON COLUMN batch_jobs.config_json IS 'Batch job configuration and metadata';

-- ================================================================================================
-- CHILD JOBS TABLE
-- Individual training jobs that belong to a batch
-- ================================================================================================
CREATE TABLE IF NOT EXISTS child_jobs (
    -- Primary identifiers
    id UUID PRIMARY KEY,
    batch_id UUID NOT NULL REFERENCES batch_jobs(id) ON DELETE CASCADE,
    
    -- Job metadata
    model_type VARCHAR(50) NOT NULL,
    model_weight DOUBLE PRECISION NOT NULL DEFAULT 0.25,
    
    -- Status tracking
    status VARCHAR(50) NOT NULL DEFAULT 'Pending',
    
    -- Progress tracking
    current_epoch INTEGER NOT NULL DEFAULT 0,
    total_epochs INTEGER NOT NULL DEFAULT 0,
    progress_pct DOUBLE PRECISION NOT NULL DEFAULT 0.0,
    
    -- Configuration
    config_json JSONB NOT NULL DEFAULT '{}'::jsonb,
    
    -- Error tracking
    error_message TEXT,
    
    -- Audit timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    started_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    -- Constraints
    CONSTRAINT chk_child_job_status CHECK (
        status IN ('Pending', 'Running', 'Completed', 'Failed', 'Stopped', 'Paused')
    ),
    CONSTRAINT chk_child_job_model_type CHECK (
        model_type IN ('DQN', 'PPO', 'MAMBA-2', 'TFT', 'TLOB')
    ),
    CONSTRAINT chk_child_job_weight CHECK (
        model_weight >= 0.0 AND model_weight <= 1.0
    ),
    CONSTRAINT chk_child_job_progress CHECK (
        progress_pct >= 0.0 AND progress_pct <= 100.0
    ),
    CONSTRAINT chk_child_job_epochs CHECK (
        current_epoch >= 0 AND
        total_epochs >= 0 AND
        current_epoch <= total_epochs
    )
);

-- Add table comment
COMMENT ON TABLE child_jobs IS 'Individual child training jobs that belong to a parent batch job';

-- Add column comments
COMMENT ON COLUMN child_jobs.id IS 'Unique child job identifier';
COMMENT ON COLUMN child_jobs.batch_id IS 'Parent batch job identifier';
COMMENT ON COLUMN child_jobs.model_type IS 'Type of ML model (DQN, PPO, MAMBA-2, TFT, TLOB)';
COMMENT ON COLUMN child_jobs.model_weight IS 'Weight for progress aggregation (0.0-1.0)';
COMMENT ON COLUMN child_jobs.status IS 'Current job status (Pending, Running, Completed, Failed, Stopped, Paused)';
COMMENT ON COLUMN child_jobs.current_epoch IS 'Current training epoch (0-based)';
COMMENT ON COLUMN child_jobs.total_epochs IS 'Total number of training epochs';
COMMENT ON COLUMN child_jobs.progress_pct IS 'Individual job progress (0.0-100.0)';
COMMENT ON COLUMN child_jobs.config_json IS 'Child job configuration and metadata';

-- ================================================================================================
-- HIGH-PERFORMANCE INDEXES
-- ================================================================================================

-- Index for batch job queries
CREATE INDEX IF NOT EXISTS idx_batch_jobs_status
    ON batch_jobs(status);

CREATE INDEX IF NOT EXISTS idx_batch_jobs_created_at
    ON batch_jobs(created_at DESC);

-- Index for child job queries
CREATE INDEX IF NOT EXISTS idx_child_jobs_batch_id
    ON child_jobs(batch_id);

CREATE INDEX IF NOT EXISTS idx_child_jobs_status
    ON child_jobs(status);

CREATE INDEX IF NOT EXISTS idx_child_jobs_model_type
    ON child_jobs(model_type);

-- Composite index for batch summary queries
CREATE INDEX IF NOT EXISTS idx_child_jobs_batch_status
    ON child_jobs(batch_id, status);

-- GIN indexes for JSONB queries
CREATE INDEX IF NOT EXISTS idx_batch_jobs_config_gin
    ON batch_jobs USING GIN (config_json);

CREATE INDEX IF NOT EXISTS idx_child_jobs_config_gin
    ON child_jobs USING GIN (config_json);

-- ================================================================================================
-- TRIGGER FUNCTIONS FOR DATA INTEGRITY
-- ================================================================================================

-- Function to update child job updated_at timestamp
CREATE OR REPLACE FUNCTION update_child_jobs_timestamp()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at := NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger to auto-update updated_at on modification
CREATE TRIGGER tg_child_jobs_update_timestamp
    BEFORE UPDATE ON child_jobs
    FOR EACH ROW
    EXECUTE FUNCTION update_child_jobs_timestamp();

-- Function to update batch job counts and progress
CREATE OR REPLACE FUNCTION update_batch_job_aggregates()
RETURNS TRIGGER AS $$
DECLARE
    v_batch_id UUID;
    v_total_jobs INTEGER;
    v_pending_jobs INTEGER;
    v_running_jobs INTEGER;
    v_completed_jobs INTEGER;
    v_failed_jobs INTEGER;
    v_weighted_progress DOUBLE PRECISION;
    v_new_status VARCHAR(50);
BEGIN
    -- Determine which batch to update
    v_batch_id := COALESCE(NEW.batch_id, OLD.batch_id);
    
    -- Count jobs by status
    SELECT
        COUNT(*),
        COUNT(*) FILTER (WHERE status = 'Pending'),
        COUNT(*) FILTER (WHERE status = 'Running'),
        COUNT(*) FILTER (WHERE status = 'Completed'),
        COUNT(*) FILTER (WHERE status = 'Failed')
    INTO
        v_total_jobs,
        v_pending_jobs,
        v_running_jobs,
        v_completed_jobs,
        v_failed_jobs
    FROM child_jobs
    WHERE batch_id = v_batch_id;
    
    -- Calculate weighted progress
    SELECT COALESCE(SUM(progress_pct * model_weight), 0.0)
    INTO v_weighted_progress
    FROM child_jobs
    WHERE batch_id = v_batch_id;
    
    -- Determine batch status
    IF v_failed_jobs > 0 AND v_running_jobs = 0 AND v_pending_jobs = 0 THEN
        v_new_status := 'Failed';
    ELSIF v_completed_jobs = v_total_jobs AND v_total_jobs > 0 THEN
        v_new_status := 'Completed';
    ELSIF v_running_jobs > 0 THEN
        v_new_status := 'Running';
    ELSE
        v_new_status := 'Pending';
    END IF;
    
    -- Update batch job
    UPDATE batch_jobs
    SET
        total_jobs = v_total_jobs,
        pending_jobs = v_pending_jobs,
        running_jobs = v_running_jobs,
        completed_jobs = v_completed_jobs,
        failed_jobs = v_failed_jobs,
        overall_progress = v_weighted_progress,
        status = v_new_status,
        started_at = CASE
            WHEN started_at IS NULL AND v_running_jobs > 0 THEN NOW()
            ELSE started_at
        END,
        completed_at = CASE
            WHEN v_new_status IN ('Completed', 'Failed') THEN NOW()
            ELSE NULL
        END
    WHERE id = v_batch_id;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Trigger to update batch aggregates on child job changes
CREATE TRIGGER tg_child_jobs_update_batch_aggregates
    AFTER INSERT OR UPDATE OR DELETE ON child_jobs
    FOR EACH ROW
    EXECUTE FUNCTION update_batch_job_aggregates();

-- ================================================================================================
-- ANALYTICAL VIEWS FOR REPORTING
-- ================================================================================================

-- View for active batch jobs
CREATE OR REPLACE VIEW v_active_batch_jobs AS
SELECT
    b.id,
    b.name,
    b.description,
    b.status,
    b.total_jobs,
    b.pending_jobs,
    b.running_jobs,
    b.completed_jobs,
    b.failed_jobs,
    b.overall_progress,
    b.created_at,
    b.started_at,
    b.completed_at,
    EXTRACT(EPOCH FROM (COALESCE(b.completed_at, NOW()) - b.created_at)) as duration_seconds
FROM batch_jobs b
WHERE b.status IN ('Pending', 'Running')
ORDER BY b.created_at DESC;

COMMENT ON VIEW v_active_batch_jobs IS 'Active batch jobs (Pending or Running) with progress metrics';

-- View for batch job details with child job breakdown
CREATE OR REPLACE VIEW v_batch_job_details AS
SELECT
    b.id as batch_id,
    b.name as batch_name,
    b.status as batch_status,
    b.overall_progress,
    c.id as child_job_id,
    c.model_type,
    c.model_weight,
    c.status as child_status,
    c.progress_pct,
    c.current_epoch,
    c.total_epochs,
    c.error_message
FROM batch_jobs b
LEFT JOIN child_jobs c ON b.id = c.batch_id
ORDER BY b.created_at DESC, c.model_type;

COMMENT ON VIEW v_batch_job_details IS 'Detailed view of batch jobs with all child jobs';

-- ================================================================================================
-- FINAL VALIDATION
-- ================================================================================================

DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.tables
        WHERE table_name = 'batch_jobs'
    ) THEN
        RAISE EXCEPTION 'Migration 046 failed: batch_jobs table not created';
    END IF;
    
    IF NOT EXISTS (
        SELECT 1 FROM information_schema.tables
        WHERE table_name = 'child_jobs'
    ) THEN
        RAISE EXCEPTION 'Migration 046 failed: child_jobs table not created';
    END IF;
    
    RAISE NOTICE 'Migration 046 completed successfully: Batch job tracking schema created';
END $$;
