CREATE TABLE IF NOT EXISTS vpn_connections (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    target_endpoint JSONB NOT NULL DEFAULT '{}'::JSONB,
    protocol_family TEXT NOT NULL DEFAULT 'generic',
    credential_ref TEXT,
    mode TEXT NOT NULL DEFAULT 'single_active',
    desired_state TEXT NOT NULL DEFAULT 'disabled',
    allowed_node_policy JSONB NOT NULL DEFAULT '{"mode":"explicit","node_ids":[]}'::JSONB,
    routing_usage JSONB NOT NULL DEFAULT '[]'::JSONB,
    route_policy JSONB NOT NULL DEFAULT '{}'::JSONB,
    qos_policy JSONB NOT NULL DEFAULT '{}'::JSONB,
    placement_policy JSONB NOT NULL DEFAULT '{}'::JSONB,
    status TEXT NOT NULL DEFAULT 'disabled',
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    updated_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT vpn_connections_mode_check
        CHECK (mode IN ('single_active')),
    CONSTRAINT vpn_connections_desired_state_check
        CHECK (desired_state IN ('enabled', 'disabled')),
    CONSTRAINT vpn_connections_status_check
        CHECK (status IN ('disabled', 'enabled', 'connecting', 'active', 'degraded', 'failed')),
    CONSTRAINT vpn_connections_protocol_family_check
        CHECK (protocol_family IN ('generic', 'wireguard', 'ipsec', 'openvpn'))
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_vpn_connections_cluster_org_name
    ON vpn_connections(cluster_id, organization_id, lower(name));

CREATE INDEX IF NOT EXISTS idx_vpn_connections_cluster_org_state
    ON vpn_connections(cluster_id, organization_id, desired_state, status);

CREATE TABLE IF NOT EXISTS vpn_connection_allowed_nodes (
    vpn_connection_id UUID NOT NULL REFERENCES vpn_connections(id) ON DELETE CASCADE,
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    role_preference TEXT NOT NULL DEFAULT 'candidate',
    status TEXT NOT NULL DEFAULT 'active',
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    created_by_user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (vpn_connection_id, node_id),
    CONSTRAINT vpn_connection_allowed_nodes_membership_fk
        FOREIGN KEY (cluster_id, node_id)
        REFERENCES cluster_memberships(cluster_id, node_id)
        ON DELETE CASCADE,
    CONSTRAINT vpn_connection_allowed_nodes_preference_check
        CHECK (role_preference IN ('candidate', 'standby', 'preferred')),
    CONSTRAINT vpn_connection_allowed_nodes_status_check
        CHECK (status IN ('active', 'disabled'))
);

CREATE INDEX IF NOT EXISTS idx_vpn_connection_allowed_nodes_cluster_node
    ON vpn_connection_allowed_nodes(cluster_id, node_id, status);

CREATE TABLE IF NOT EXISTS vpn_connection_route_policies (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    vpn_connection_id UUID NOT NULL REFERENCES vpn_connections(id) ON DELETE CASCADE,
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
    route_type TEXT NOT NULL,
    destination TEXT NOT NULL,
    action TEXT NOT NULL DEFAULT 'allow',
    service_type TEXT,
    priority INTEGER NOT NULL DEFAULT 100,
    policy JSONB NOT NULL DEFAULT '{}'::JSONB,
    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(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    CONSTRAINT vpn_connection_route_policies_route_type_check
        CHECK (route_type IN ('cidr', 'dns_suffix', 'service', 'resource')),
    CONSTRAINT vpn_connection_route_policies_action_check
        CHECK (action IN ('allow', 'deny')),
    CONSTRAINT vpn_connection_route_policies_status_check
        CHECK (status IN ('active', 'disabled'))
);

CREATE INDEX IF NOT EXISTS idx_vpn_connection_route_policies_connection
    ON vpn_connection_route_policies(vpn_connection_id, status, priority);

CREATE INDEX IF NOT EXISTS idx_vpn_connection_route_policies_cluster_org
    ON vpn_connection_route_policies(cluster_id, organization_id, route_type, status);

CREATE TABLE IF NOT EXISTS vpn_connection_leases (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    vpn_connection_id UUID NOT NULL REFERENCES vpn_connections(id) ON DELETE CASCADE,
    cluster_id UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
    owner_node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
    lease_generation BIGINT NOT NULL,
    fencing_token TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'active',
    acquired_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    renewed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMPTZ NOT NULL,
    released_at TIMESTAMPTZ,
    fenced_at TIMESTAMPTZ,
    metadata JSONB NOT NULL DEFAULT '{}'::JSONB,
    CONSTRAINT vpn_connection_leases_membership_fk
        FOREIGN KEY (cluster_id, owner_node_id)
        REFERENCES cluster_memberships(cluster_id, node_id)
        ON DELETE CASCADE,
    CONSTRAINT vpn_connection_leases_status_check
        CHECK (status IN ('active', 'released', 'expired', 'fenced')),
    CONSTRAINT vpn_connection_leases_expiry_check
        CHECK (expires_at > acquired_at)
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_vpn_connection_leases_generation
    ON vpn_connection_leases(vpn_connection_id, lease_generation);

CREATE UNIQUE INDEX IF NOT EXISTS idx_vpn_connection_leases_single_active
    ON vpn_connection_leases(vpn_connection_id)
    WHERE status = 'active';

CREATE INDEX IF NOT EXISTS idx_vpn_connection_leases_owner
    ON vpn_connection_leases(cluster_id, owner_node_id, status, expires_at);

CREATE INDEX IF NOT EXISTS idx_vpn_connection_leases_connection_time
    ON vpn_connection_leases(vpn_connection_id, acquired_at DESC);
