CREATE TABLE IF NOT EXISTS clusters (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    slug TEXT NOT NULL UNIQUE,
    name TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    region TEXT,
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT clusters_status_check
        CHECK (status IN ('active', 'disabled', 'archived', 'degraded'))
);

INSERT INTO clusters (slug, name, status, region, metadata)
VALUES ('default', 'Default Cluster', 'active', NULL, '{"bootstrap":true}'::jsonb)
ON CONFLICT (slug) DO NOTHING;

CREATE TABLE IF NOT EXISTS cluster_memberships (
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    membership_status TEXT NOT NULL DEFAULT 'active',
    joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_seen_at TIMESTAMPTZ,
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    PRIMARY KEY (cluster_id, node_id),
    CONSTRAINT cluster_memberships_status_check
        CHECK (membership_status IN ('active', 'draining', 'disabled', 'revoked'))
);

INSERT INTO cluster_memberships (cluster_id, node_id, membership_status, joined_at, last_seen_at, metadata)
SELECT c.id, n.id, 'active', COALESCE(n.created_at, NOW()), n.last_seen_at, '{"backfilled":true}'::jsonb
FROM clusters c
CROSS JOIN nodes n
WHERE c.slug = 'default'
ON CONFLICT (cluster_id, node_id) DO NOTHING;

CREATE TABLE IF NOT EXISTS node_identities (
    node_id UUID PRIMARY KEY REFERENCES nodes(id) ON DELETE CASCADE,
    public_key TEXT NOT NULL,
    certificate_serial TEXT,
    certificate_not_before TIMESTAMPTZ,
    certificate_not_after TIMESTAMPTZ,
    identity_status TEXT NOT NULL DEFAULT 'pending',
    rotated_at TIMESTAMPTZ,
    revoked_at TIMESTAMPTZ,
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT node_identities_status_check
        CHECK (identity_status IN ('pending', 'active', 'rotating', 'revoked'))
);

CREATE TABLE IF NOT EXISTS node_join_tokens (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    token_hash TEXT NOT NULL UNIQUE,
    scope JSONB NOT NULL DEFAULT '{}'::JSONB,
    expires_at TIMESTAMPTZ NOT NULL,
    max_uses INTEGER NOT NULL DEFAULT 1,
    used_count INTEGER NOT NULL DEFAULT 0,
    status TEXT NOT NULL DEFAULT 'active',
    created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    revoked_at TIMESTAMPTZ,
    CONSTRAINT node_join_tokens_status_check
        CHECK (status IN ('active', 'revoked', 'expired')),
    CONSTRAINT node_join_tokens_max_uses_check
        CHECK (max_uses > 0),
    CONSTRAINT node_join_tokens_used_count_check
        CHECK (used_count >= 0)
);

CREATE INDEX IF NOT EXISTS idx_node_join_tokens_cluster_status
    ON node_join_tokens(cluster_id, status, expires_at);

CREATE TABLE IF NOT EXISTS node_join_requests (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    join_token_id UUID REFERENCES node_join_tokens(id) ON DELETE SET NULL,
    node_name TEXT NOT NULL,
    node_fingerprint TEXT NOT NULL,
    public_key TEXT NOT NULL,
    reported_capabilities JSONB NOT NULL DEFAULT '{}'::JSONB,
    reported_facts JSONB NOT NULL DEFAULT '{}'::JSONB,
    requested_roles JSONB NOT NULL DEFAULT '[]'::JSONB,
    status TEXT NOT NULL DEFAULT 'pending',
    reviewed_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    reviewed_at TIMESTAMPTZ,
    approved_node_id UUID REFERENCES nodes(id) ON DELETE SET NULL,
    rejection_reason TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT node_join_requests_status_check
        CHECK (status IN ('pending', 'approved', 'rejected', 'cancelled'))
);

CREATE INDEX IF NOT EXISTS idx_node_join_requests_cluster_status
    ON node_join_requests(cluster_id, status, created_at DESC);

CREATE UNIQUE INDEX IF NOT EXISTS idx_node_join_requests_pending_fingerprint
    ON node_join_requests(cluster_id, node_fingerprint)
    WHERE status = 'pending';

CREATE TABLE IF NOT EXISTS node_role_assignments (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    organization_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
    role TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    policy JSONB NOT NULL DEFAULT '{}'::JSONB,
    assigned_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    assigned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    revoked_at TIMESTAMPTZ,
    CONSTRAINT node_role_assignments_status_check
        CHECK (status IN ('active', 'disabled', 'revoked')),
    CONSTRAINT node_role_assignments_role_check
        CHECK (role IN (
            'entry-node',
            'relay-node',
            'core-mesh',
            'rdp-worker',
            'vnc-worker',
            'vpn-exit',
            'vpn-connector',
            'file-storage-cache',
            'update-cache',
            'video-relay'
        ))
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_node_role_assignments_unique_active
    ON node_role_assignments(cluster_id, node_id, role, COALESCE(organization_id, '00000000-0000-0000-0000-000000000000'::uuid))
    WHERE status = 'active';

CREATE INDEX IF NOT EXISTS idx_node_role_assignments_cluster
    ON node_role_assignments(cluster_id, role, status);

CREATE TABLE IF NOT EXISTS node_heartbeats (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    health_status TEXT NOT NULL DEFAULT 'unknown',
    reported_version TEXT,
    capabilities JSONB NOT NULL DEFAULT '{}'::JSONB,
    service_states JSONB NOT NULL DEFAULT '{}'::JSONB,
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT node_heartbeats_health_status_check
        CHECK (health_status IN ('unknown', 'healthy', 'warning', 'critical'))
);

CREATE INDEX IF NOT EXISTS idx_node_heartbeats_cluster_node_observed
    ON node_heartbeats(cluster_id, node_id, observed_at DESC);

CREATE TABLE IF NOT EXISTS node_latest_heartbeats (
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    heartbeat_id UUID REFERENCES node_heartbeats(id) ON DELETE SET NULL,
    health_status TEXT NOT NULL DEFAULT 'unknown',
    reported_version TEXT,
    capabilities JSONB NOT NULL DEFAULT '{}'::JSONB,
    service_states JSONB NOT NULL DEFAULT '{}'::JSONB,
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    observed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (cluster_id, node_id),
    CONSTRAINT node_latest_heartbeats_health_status_check
        CHECK (health_status IN ('unknown', 'healthy', 'warning', 'critical'))
);

CREATE TABLE IF NOT EXISTS cluster_audit_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cluster_id UUID REFERENCES clusters(id) ON DELETE SET NULL,
    actor_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    event_type TEXT NOT NULL,
    target_type TEXT NOT NULL,
    target_id TEXT,
    payload JSONB NOT NULL DEFAULT '{}'::JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_cluster_audit_events_cluster_created
    ON cluster_audit_events(cluster_id, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_cluster_audit_events_type_created
    ON cluster_audit_events(event_type, created_at DESC);
