-- ============================================================================
-- SCRIPT: 02_maintenance.sql
-- DESCRIPTION: Core functions for Zabbix partitioning (Create, Drop, Maintain).
-- ============================================================================

-- Function to check if a partition exists
CREATE OR REPLACE FUNCTION partitions.partition_exists(p_partition_name text) 
RETURNS boolean AS $$
BEGIN
    RETURN EXISTS (
        SELECT 1 FROM pg_class c
        JOIN pg_namespace n ON n.oid = c.relnamespace
        WHERE c.relname = p_partition_name
        AND n.nspname = 'public'
    );
END;
$$ LANGUAGE plpgsql;

-- Function to create a partition
CREATE OR REPLACE PROCEDURE partitions.create_partition(
    p_parent_table text,
    p_start_time timestamp with time zone,
    p_end_time timestamp with time zone,
    p_period text
) LANGUAGE plpgsql AS $$
DECLARE
    v_partition_name text;
    v_start_ts bigint;
    v_end_ts bigint;
    v_suffix text;
BEGIN
    v_start_ts := extract(epoch from p_start_time)::bigint;
    v_end_ts := extract(epoch from p_end_time)::bigint;
    
    IF p_period = 'month' THEN
        v_suffix := to_char(p_start_time, 'YYYYMM');
    ELSE
        v_suffix := to_char(p_start_time, 'YYYYMMDD');
    END IF;
    
    v_partition_name := p_parent_table || '_p' || v_suffix;

    IF NOT partitions.partition_exists(v_partition_name) THEN
        EXECUTE format(
            'CREATE TABLE public.%I PARTITION OF public.%I FOR VALUES FROM (%s) TO (%s)',
            v_partition_name, p_parent_table, v_start_ts, v_end_ts
        );
    END IF;
END;
$$;

-- Function to drop old partitions
CREATE OR REPLACE PROCEDURE partitions.drop_old_partitions(
    p_parent_table text,
    p_retention interval,
    p_period text
) LANGUAGE plpgsql AS $$
DECLARE
    v_cutoff_ts bigint;
    v_partition record;
    v_partition_date timestamp with time zone;
    v_suffix text;
BEGIN
    -- Calculate cutoff timestamp
    v_cutoff_ts := extract(epoch from (now() - p_retention))::bigint;

    FOR v_partition IN 
        SELECT 
            child.relname AS partition_name
        FROM pg_inherits
        JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
        JOIN pg_class child ON pg_inherits.inhrelid = child.oid
        WHERE parent.relname = p_parent_table
    LOOP
        -- Parse partition suffix to determine age
        -- Format: parent_pYYYYMM or parent_pYYYYMMDD
        v_suffix := substring(v_partition.partition_name from length(p_parent_table) + 3);
        
        BEGIN
            IF length(v_suffix) = 6 THEN -- YYYYMM
                v_partition_date := to_timestamp(v_suffix || '01', 'YYYYMMDD');
                -- For monthly, we check if the END of the month is older than retention? 
                -- Or just strict retention. 
                -- To be safe, adding 1 month to check vs cutoff.
                IF extract(epoch from (v_partition_date + '1 month'::interval)) < v_cutoff_ts THEN
                    RAISE NOTICE 'Dropping old partition %', v_partition.partition_name;
                    EXECUTE format('DROP TABLE public.%I', v_partition.partition_name);
                END IF;
            ELSIF length(v_suffix) = 8 THEN -- YYYYMMDD
                v_partition_date := to_timestamp(v_suffix, 'YYYYMMDD');
                IF extract(epoch from (v_partition_date + '1 day'::interval)) < v_cutoff_ts THEN
                    RAISE NOTICE 'Dropping old partition %', v_partition.partition_name;
                    EXECUTE format('DROP TABLE public.%I', v_partition.partition_name);
                END IF;
            END IF;
        EXCEPTION WHEN OTHERS THEN
            -- Ignore parsing errors for non-standard partitions
            NULL;
        END;
    END LOOP;
END;
$$;

-- MAIN Procedure to maintain a single table
CREATE OR REPLACE PROCEDURE partitions.maintain_table(
    p_table_name text,
    p_period text,
    p_keep_history interval
) LANGUAGE plpgsql AS $$
DECLARE
    v_start_time timestamp with time zone;
    v_period_interval interval;
    i integer;
    v_past_iterations integer;
BEGIN
    IF p_period = 'day' THEN
        v_period_interval := '1 day'::interval;
        v_start_time := date_trunc('day', now());
        -- Calculate how many past days cover the retention period
        v_past_iterations := extract(day from p_keep_history)::integer;
        -- Safety cap or ensure minimum? default 7 if null?
        IF v_past_iterations IS NULL THEN v_past_iterations := 7; END IF;
        
    ELSIF p_period = 'week' THEN
        v_period_interval := '1 week'::interval;
        v_start_time := date_trunc('week', now());
        v_past_iterations := (extract(day from p_keep_history) / 7)::integer;

    ELSIF p_period = 'month' THEN
        v_period_interval := '1 month'::interval;
        v_start_time := date_trunc('month', now());
        -- Approximate months
        v_past_iterations := (extract(year from p_keep_history) * 12 + extract(month from p_keep_history))::integer;
        -- Fallback if interval is just days (e.g. '365 days')
        IF v_past_iterations = 0 THEN 
             v_past_iterations := (extract(day from p_keep_history) / 30)::integer; 
        END IF;
    ELSE
        RETURN;
    END IF;

    -- 1. Create Future Partitions (Current + 3 ahead)
    FOR i IN 0..3 LOOP
        CALL partitions.create_partition(
            p_table_name,
            v_start_time + (i * v_period_interval),
            v_start_time + ((i + 1) * v_period_interval),
            p_period
        );
    END LOOP;
    
    -- 2. Create Past Partitions (Covering retention period)
    IF v_past_iterations > 0 THEN
        FOR i IN 1..v_past_iterations LOOP
            CALL partitions.create_partition(
                p_table_name,
                v_start_time - (i * v_period_interval),
                v_start_time - ((i - 1) * v_period_interval),
                p_period
            );
        END LOOP;
    END IF;

    -- 3. Drop Old Partitions
    CALL partitions.drop_old_partitions(p_table_name, p_keep_history, p_period);
    
    -- 4. Update Metadata
    UPDATE partitions.config SET last_updated = now() WHERE table_name = p_table_name;
END;
$$;

-- Global Maintenance Procedure
CREATE OR REPLACE PROCEDURE partitions.run_maintenance() 
LANGUAGE plpgsql AS $$
DECLARE
    v_row record;
BEGIN
    FOR v_row IN SELECT * FROM partitions.config LOOP
        CALL partitions.maintain_table(v_row.table_name, v_row.period, v_row.keep_history);
    END LOOP;
END;
$$;
