-- ============================================
-- Multi-Instance Support Migration
-- Phase 1: Instance Registration
-- Phase 2: Distributed Locking  
-- Phase 6: File System Monitor Coordination
-- ============================================

-- Run this script to add multi-instance support to existing database
-- Usage: psql -U jellyfin -d jellyfin -f add_multi_instance_support.sql

BEGIN;

-- 1. Create Instances table
CREATE TABLE IF NOT EXISTS library."Instances" (
    "InstanceId" UUID PRIMARY KEY,
    "Hostname" VARCHAR(255) NOT NULL,
    "ProcessId" INTEGER NOT NULL,
    "HttpPort" INTEGER NOT NULL,
    "HttpsPort" INTEGER,
    "Version" VARCHAR(50) NOT NULL,
    "StartedAt" TIMESTAMP NOT NULL DEFAULT NOW(),
    "LastHeartbeat" TIMESTAMP NOT NULL DEFAULT NOW(),
    "Status" VARCHAR(50) NOT NULL DEFAULT 'Active',
    "IsPrimary" BOOLEAN NOT NULL DEFAULT FALSE,
    "Capabilities" TEXT NOT NULL DEFAULT '{}',
    "Configuration" TEXT NOT NULL DEFAULT '{}',
    "RowVersion" INTEGER NOT NULL DEFAULT 0,
    CONSTRAINT chk_instance_status CHECK ("Status" IN ('Active', 'Shutdown', 'Failed', 'Maintenance'))
);

CREATE INDEX IF NOT EXISTS idx_instances_lastheartbeat ON library."Instances"("LastHeartbeat");
CREATE INDEX IF NOT EXISTS idx_instances_status ON library."Instances"("Status");
CREATE INDEX IF NOT EXISTS idx_instances_isprimary ON library."Instances"("IsPrimary") WHERE "IsPrimary" = TRUE;
CREATE INDEX IF NOT EXISTS idx_instances_host_process ON library."Instances"("Hostname", "ProcessId");

-- 2. Create Distributed Locks table
CREATE TABLE IF NOT EXISTS library."DistributedLocks" (
    "LockName" VARCHAR(255) PRIMARY KEY,
    "InstanceId" UUID NOT NULL,
    "AcquiredAt" TIMESTAMP NOT NULL DEFAULT NOW(),
    "ExpiresAt" TIMESTAMP NOT NULL,
    "RenewedAt" TIMESTAMP NOT NULL DEFAULT NOW(),
    "RowVersion" INTEGER NOT NULL DEFAULT 0,
    CONSTRAINT fk_lock_instance FOREIGN KEY ("InstanceId") 
        REFERENCES library."Instances"("InstanceId") ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_locks_expiration ON library."DistributedLocks"("ExpiresAt");
CREATE INDEX IF NOT EXISTS idx_locks_instance ON library."DistributedLocks"("InstanceId");
CREATE INDEX IF NOT EXISTS idx_locks_expiration_instance ON library."DistributedLocks"("ExpiresAt", "InstanceId");

-- 3. Add InstanceId to ActivityLog
ALTER TABLE activitylog."ActivityLog" ADD COLUMN IF NOT EXISTS "InstanceId" UUID;
CREATE INDEX IF NOT EXISTS idx_activitylog_instance ON activitylog."ActivityLog"("InstanceId");

-- 4. Add InstanceId to Devices for session tracking
ALTER TABLE library."Devices" ADD COLUMN IF NOT EXISTS "InstanceId" UUID;
CREATE INDEX IF NOT EXISTS idx_devices_instance ON library."Devices"("InstanceId");

-- 5. Create File System Changes table (Phase 6)
CREATE TABLE IF NOT EXISTS library."FileSystemChanges" (
    "Id" BIGSERIAL PRIMARY KEY,
    "Path" TEXT NOT NULL,
    "ChangeType" VARCHAR(50) NOT NULL,
    "DetectedAt" TIMESTAMP NOT NULL DEFAULT NOW(),
    "DetectedBy" UUID NOT NULL,
    "ProcessedAt" TIMESTAMP,
    "ProcessedBy" UUID,
    "LibraryId" UUID,
    "Error" TEXT,
    "OldPath" TEXT,
    CONSTRAINT fk_fschange_detectedby FOREIGN KEY ("DetectedBy") 
        REFERENCES library."Instances"("InstanceId") ON DELETE CASCADE,
    CONSTRAINT fk_fschange_processedby FOREIGN KEY ("ProcessedBy") 
        REFERENCES library."Instances"("InstanceId") ON DELETE SET NULL,
    CONSTRAINT chk_filesystemchange_type CHECK ("ChangeType" IN ('Created', 'Modified', 'Deleted', 'Renamed'))
);

CREATE INDEX IF NOT EXISTS idx_filesystemchanges_unprocessed ON library."FileSystemChanges"("ProcessedAt") 
    WHERE "ProcessedAt" IS NULL;
CREATE INDEX IF NOT EXISTS idx_filesystemchanges_detectedat ON library."FileSystemChanges"("DetectedAt");
CREATE INDEX IF NOT EXISTS idx_filesystemchanges_path ON library."FileSystemChanges"("Path");
CREATE INDEX IF NOT EXISTS idx_filesystemchanges_library ON library."FileSystemChanges"("LibraryId") 
    WHERE "LibraryId" IS NOT NULL;

-- 6. Function to cleanup stale instances
CREATE OR REPLACE FUNCTION library.cleanup_stale_instances()
RETURNS INTEGER AS $$
DECLARE
    updated_count INTEGER;
BEGIN
    UPDATE library."Instances"
    SET "Status" = 'Failed'
    WHERE "Status" = 'Active'
      AND "LastHeartbeat" < NOW() - INTERVAL '2 minutes';

    GET DIAGNOSTICS updated_count = ROW_COUNT;
    RETURN updated_count;
END;
$$ LANGUAGE plpgsql;

-- 7. Function to get primary instance
CREATE OR REPLACE FUNCTION library.get_primary_instance()
RETURNS UUID AS $$
DECLARE
    primary_id UUID;
BEGIN
    SELECT "InstanceId" INTO primary_id
    FROM library."Instances"
    WHERE "Status" = 'Active'
      AND "IsPrimary" = TRUE
      AND "LastHeartbeat" > NOW() - INTERVAL '1 minute'
    LIMIT 1;

    RETURN primary_id;
END;
$$ LANGUAGE plpgsql;

-- 8. Function to elect primary instance
CREATE OR REPLACE FUNCTION library.elect_primary_instance()
RETURNS UUID AS $$
DECLARE
    elected_id UUID;
BEGIN
    -- Clear any existing primary that's not active
    UPDATE library."Instances"
    SET "IsPrimary" = FALSE
    WHERE "IsPrimary" = TRUE
      AND ("Status" != 'Active' OR "LastHeartbeat" < NOW() - INTERVAL '1 minute');

    -- Check if we have an active primary
    SELECT "InstanceId" INTO elected_id
    FROM library."Instances"
    WHERE "Status" = 'Active'
      AND "IsPrimary" = TRUE
      AND "LastHeartbeat" > NOW() - INTERVAL '1 minute'
    LIMIT 1;

    -- If no primary, elect the oldest active instance
    IF elected_id IS NULL THEN
        SELECT "InstanceId" INTO elected_id
        FROM library."Instances"
        WHERE "Status" = 'Active'
          AND "LastHeartbeat" > NOW() - INTERVAL '1 minute'
        ORDER BY "StartedAt" ASC
        LIMIT 1;

        IF elected_id IS NOT NULL THEN
            UPDATE library."Instances"
            SET "IsPrimary" = TRUE
            WHERE "InstanceId" = elected_id;
        END IF;
    END IF;

    RETURN elected_id;
END;
$$ LANGUAGE plpgsql;

-- 9. Function to cleanup old processed file system changes
CREATE OR REPLACE FUNCTION library.cleanup_old_filesystem_changes()
RETURNS INTEGER AS $$
DECLARE
    deleted_count INTEGER;
BEGIN
    -- Delete processed changes older than 7 days
    DELETE FROM library."FileSystemChanges"
    WHERE "ProcessedAt" IS NOT NULL
      AND "ProcessedAt" < NOW() - INTERVAL '7 days';

    GET DIAGNOSTICS deleted_count = ROW_COUNT;
    RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;

-- 10. Grant permissions
GRANT ALL ON TABLE library."Instances" TO jellyfin;
GRANT ALL ON TABLE library."DistributedLocks" TO jellyfin;
GRANT ALL ON TABLE library."FileSystemChanges" TO jellyfin;
GRANT ALL ON SEQUENCE library."FileSystemChanges_Id_seq" TO jellyfin;
GRANT EXECUTE ON FUNCTION library.cleanup_stale_instances() TO jellyfin;
GRANT EXECUTE ON FUNCTION library.get_primary_instance() TO jellyfin;
GRANT EXECUTE ON FUNCTION library.elect_primary_instance() TO jellyfin;
GRANT EXECUTE ON FUNCTION library.cleanup_old_filesystem_changes() TO jellyfin;

COMMIT;

-- Verification queries
\echo '--- Verification ---'
\echo 'Checking Instances table...'
SELECT COUNT(*) AS instance_count FROM library."Instances";

\echo 'Checking DistributedLocks table...'
SELECT COUNT(*) AS lock_count FROM library."DistributedLocks";

\echo 'Checking FileSystemChanges table...'
SELECT COUNT(*) AS fschange_count FROM library."FileSystemChanges";

\echo 'Checking Devices table for InstanceId column...'
SELECT COUNT(*) AS devices_with_instance FROM library."Devices" WHERE "InstanceId" IS NOT NULL;

\echo 'Testing cleanup function...'
SELECT library.cleanup_stale_instances() AS stale_instances_cleaned;

\echo ''
\echo '✅ Multi-instance support tables and functions created successfully!'
\echo 'Next step: Start Jellyfin instances with EnableMultiInstance=true in startup.json'
