-- AI Compliance SDK - RBAC Schema
-- Migration 001: Multi-Tenant RBAC with Namespace Isolation

-- Enable UUID extension
CREATE EXTENSION IF NOT EXISTS "pgcrypto";

-- ============================================================================
-- Tenants (Mandanten)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_tenants (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    slug VARCHAR(100) NOT NULL UNIQUE,
    settings JSONB DEFAULT '{}',
    max_users INT DEFAULT 100,
    llm_quota_monthly INT DEFAULT 10000,
    status VARCHAR(50) DEFAULT 'active',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_compliance_tenants_slug ON compliance_tenants(slug);
CREATE INDEX idx_compliance_tenants_status ON compliance_tenants(status);

-- ============================================================================
-- Namespaces (Abteilungen - z.B. CFO Use-Case)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_namespaces (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    slug VARCHAR(100) NOT NULL,
    parent_namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE SET NULL,
    isolation_level VARCHAR(50) DEFAULT 'strict', -- 'strict', 'shared', 'public'
    data_classification VARCHAR(50) DEFAULT 'internal', -- 'public', 'internal', 'confidential', 'restricted'
    metadata JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(tenant_id, slug)
);

CREATE INDEX idx_compliance_namespaces_tenant ON compliance_namespaces(tenant_id);
CREATE INDEX idx_compliance_namespaces_parent ON compliance_namespaces(parent_namespace_id);

-- ============================================================================
-- Roles with Permissions
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_roles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID REFERENCES compliance_tenants(id) ON DELETE CASCADE,
    name VARCHAR(100) NOT NULL,
    description TEXT,
    permissions TEXT[] NOT NULL DEFAULT '{}',
    is_system_role BOOLEAN DEFAULT FALSE,
    hierarchy_level INT DEFAULT 100,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(tenant_id, name)
);

CREATE INDEX idx_compliance_roles_tenant ON compliance_roles(tenant_id);
CREATE INDEX idx_compliance_roles_system ON compliance_roles(is_system_role);

-- ============================================================================
-- System Roles (Pre-defined)
-- ============================================================================
INSERT INTO compliance_roles (name, description, permissions, is_system_role, hierarchy_level) VALUES
('compliance_executive', 'Executive mit Lesezugriff auf Compliance-Daten und LLM-Queries',
    ARRAY['compliance:*:read', 'llm:query:execute', 'audit:own:read'], TRUE, 10),
('compliance_officer', 'Compliance-Verantwortlicher mit vollem Zugriff',
    ARRAY['compliance:*', 'audit:*', 'llm:*', 'namespace:read'], TRUE, 20),
('data_protection_officer', 'Datenschutzbeauftragter',
    ARRAY['compliance:privacy:*', 'consent:*', 'dsr:*', 'audit:read', 'llm:query:execute'], TRUE, 25),
('namespace_admin', 'Administrator fuer einen Namespace',
    ARRAY['namespace:own:admin', 'compliance:own:*', 'llm:own:query', 'audit:own:read'], TRUE, 50),
('auditor', 'Auditor mit Lesezugriff',
    ARRAY['compliance:read', 'audit:log:read', 'evidence:read'], TRUE, 60),
('compliance_user', 'Standardbenutzer mit eingeschraenktem Zugriff',
    ARRAY['compliance:own:read', 'llm:own:query'], TRUE, 100)
ON CONFLICT (tenant_id, name) DO NOTHING;

-- ============================================================================
-- User-Role Assignments with Namespace Scope
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_user_roles (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL,
    role_id UUID NOT NULL REFERENCES compliance_roles(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
    namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE CASCADE,
    granted_by UUID NOT NULL,
    expires_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(user_id, role_id, tenant_id, namespace_id)
);

CREATE INDEX idx_compliance_user_roles_user ON compliance_user_roles(user_id);
CREATE INDEX idx_compliance_user_roles_tenant ON compliance_user_roles(tenant_id);
CREATE INDEX idx_compliance_user_roles_namespace ON compliance_user_roles(namespace_id);
CREATE INDEX idx_compliance_user_roles_expires ON compliance_user_roles(expires_at) WHERE expires_at IS NOT NULL;

-- ============================================================================
-- LLM Access Policies
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_llm_policies (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
    namespace_id UUID REFERENCES compliance_namespaces(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    allowed_data_categories TEXT[] DEFAULT '{}', -- 'salary', 'health', 'personal', 'financial'
    blocked_data_categories TEXT[] DEFAULT '{}',
    require_pii_redaction BOOLEAN DEFAULT TRUE,
    pii_redaction_level VARCHAR(50) DEFAULT 'strict', -- 'strict', 'moderate', 'minimal', 'none'
    allowed_models TEXT[] DEFAULT '{}', -- 'qwen2.5:7b', 'claude-3-sonnet'
    max_tokens_per_request INT DEFAULT 4000,
    max_requests_per_day INT DEFAULT 1000,
    max_requests_per_hour INT DEFAULT 100,
    is_active BOOLEAN DEFAULT TRUE,
    priority INT DEFAULT 100, -- Lower = higher priority
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_compliance_llm_policies_tenant ON compliance_llm_policies(tenant_id);
CREATE INDEX idx_compliance_llm_policies_namespace ON compliance_llm_policies(namespace_id);
CREATE INDEX idx_compliance_llm_policies_active ON compliance_llm_policies(is_active, priority);

-- ============================================================================
-- LLM Audit Log (Immutable)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_llm_audit_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES compliance_tenants(id),
    namespace_id UUID REFERENCES compliance_namespaces(id),
    user_id UUID NOT NULL,
    session_id VARCHAR(100),
    operation VARCHAR(100) NOT NULL, -- 'query', 'completion', 'embedding', 'analysis'
    model_used VARCHAR(100) NOT NULL,
    provider VARCHAR(50) NOT NULL, -- 'ollama', 'anthropic', 'openai'
    prompt_hash VARCHAR(64) NOT NULL, -- SHA-256 of prompt (no raw PII stored)
    prompt_length INT NOT NULL,
    response_length INT,
    tokens_used INT NOT NULL,
    duration_ms INT NOT NULL,
    pii_detected BOOLEAN DEFAULT FALSE,
    pii_types_detected TEXT[] DEFAULT '{}',
    pii_redacted BOOLEAN DEFAULT FALSE,
    policy_id UUID REFERENCES compliance_llm_policies(id),
    policy_violations TEXT[] DEFAULT '{}',
    data_categories_accessed TEXT[] DEFAULT '{}',
    error_message TEXT,
    request_metadata JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Partitioning-ready indexes for large audit tables
CREATE INDEX idx_llm_audit_tenant_date ON compliance_llm_audit_log(tenant_id, created_at DESC);
CREATE INDEX idx_llm_audit_user ON compliance_llm_audit_log(user_id, created_at DESC);
CREATE INDEX idx_llm_audit_namespace ON compliance_llm_audit_log(namespace_id, created_at DESC);
CREATE INDEX idx_llm_audit_operation ON compliance_llm_audit_log(operation, created_at DESC);
CREATE INDEX idx_llm_audit_pii ON compliance_llm_audit_log(pii_detected, created_at DESC) WHERE pii_detected = TRUE;
CREATE INDEX idx_llm_audit_violations ON compliance_llm_audit_log(created_at DESC) WHERE array_length(policy_violations, 1) > 0;

-- ============================================================================
-- General Audit Trail
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_audit_trail (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES compliance_tenants(id),
    namespace_id UUID REFERENCES compliance_namespaces(id),
    user_id UUID NOT NULL,
    action VARCHAR(100) NOT NULL, -- 'create', 'update', 'delete', 'access', 'export'
    resource_type VARCHAR(100) NOT NULL, -- 'role', 'namespace', 'policy', 'evidence'
    resource_id UUID,
    old_values JSONB,
    new_values JSONB,
    ip_address INET,
    user_agent TEXT,
    reason TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_audit_trail_tenant_date ON compliance_audit_trail(tenant_id, created_at DESC);
CREATE INDEX idx_audit_trail_user ON compliance_audit_trail(user_id, created_at DESC);
CREATE INDEX idx_audit_trail_resource ON compliance_audit_trail(resource_type, resource_id);

-- ============================================================================
-- API Keys for SDK Access
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_api_keys (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES compliance_tenants(id) ON DELETE CASCADE,
    name VARCHAR(255) NOT NULL,
    key_hash VARCHAR(64) NOT NULL UNIQUE, -- SHA-256 of API key
    key_prefix VARCHAR(8) NOT NULL, -- First 8 chars for identification
    permissions TEXT[] DEFAULT '{}',
    namespace_restrictions UUID[] DEFAULT '{}', -- Empty = all namespaces
    rate_limit_per_hour INT DEFAULT 1000,
    expires_at TIMESTAMPTZ,
    last_used_at TIMESTAMPTZ,
    is_active BOOLEAN DEFAULT TRUE,
    created_by UUID NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_api_keys_tenant ON compliance_api_keys(tenant_id);
CREATE INDEX idx_api_keys_prefix ON compliance_api_keys(key_prefix);
CREATE INDEX idx_api_keys_active ON compliance_api_keys(is_active, expires_at);

-- ============================================================================
-- LLM Usage Statistics (Aggregated)
-- ============================================================================
CREATE TABLE IF NOT EXISTS compliance_llm_usage_stats (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES compliance_tenants(id),
    namespace_id UUID REFERENCES compliance_namespaces(id),
    user_id UUID,
    period_start DATE NOT NULL,
    period_type VARCHAR(20) NOT NULL, -- 'daily', 'weekly', 'monthly'
    total_requests INT DEFAULT 0,
    total_tokens INT DEFAULT 0,
    total_duration_ms BIGINT DEFAULT 0,
    requests_with_pii INT DEFAULT 0,
    policy_violations INT DEFAULT 0,
    models_used JSONB DEFAULT '{}', -- {"qwen2.5:7b": 100, "claude-3-sonnet": 50}
    created_at TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(tenant_id, namespace_id, user_id, period_start, period_type)
);

CREATE INDEX idx_llm_usage_tenant_period ON compliance_llm_usage_stats(tenant_id, period_start DESC);

-- ============================================================================
-- Helper Functions
-- ============================================================================

-- Function to check if user has permission in namespace
CREATE OR REPLACE FUNCTION check_namespace_permission(
    p_user_id UUID,
    p_tenant_id UUID,
    p_namespace_id UUID,
    p_permission TEXT
) RETURNS BOOLEAN AS $$
DECLARE
    has_permission BOOLEAN := FALSE;
BEGIN
    SELECT EXISTS (
        SELECT 1
        FROM compliance_user_roles ur
        JOIN compliance_roles r ON ur.role_id = r.id
        WHERE ur.user_id = p_user_id
          AND ur.tenant_id = p_tenant_id
          AND (ur.namespace_id = p_namespace_id OR ur.namespace_id IS NULL)
          AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
          AND (
              p_permission = ANY(r.permissions)
              OR EXISTS (
                  SELECT 1 FROM unnest(r.permissions) perm
                  WHERE perm LIKE '%:*' AND p_permission LIKE replace(perm, ':*', '') || ':%'
              )
          )
    ) INTO has_permission;

    RETURN has_permission;
END;
$$ LANGUAGE plpgsql;

-- Function to get effective permissions for user in namespace
CREATE OR REPLACE FUNCTION get_effective_permissions(
    p_user_id UUID,
    p_tenant_id UUID,
    p_namespace_id UUID
) RETURNS TEXT[] AS $$
DECLARE
    permissions TEXT[];
BEGIN
    SELECT array_agg(DISTINCT perm)
    INTO permissions
    FROM (
        SELECT unnest(r.permissions) as perm
        FROM compliance_user_roles ur
        JOIN compliance_roles r ON ur.role_id = r.id
        WHERE ur.user_id = p_user_id
          AND ur.tenant_id = p_tenant_id
          AND (ur.namespace_id = p_namespace_id OR ur.namespace_id IS NULL)
          AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
    ) sub;

    RETURN COALESCE(permissions, '{}');
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- Default Tenant for Breakpilot (Self-Hosting)
-- ============================================================================
INSERT INTO compliance_tenants (name, slug, settings, max_users, llm_quota_monthly)
VALUES (
    'Breakpilot',
    'breakpilot',
    '{"deployment": "self-hosted", "hybrid_mode": true}',
    1000,
    100000
) ON CONFLICT (slug) DO NOTHING;

-- Default namespaces
INSERT INTO compliance_namespaces (tenant_id, name, slug, data_classification)
SELECT
    t.id,
    ns.name,
    ns.slug,
    ns.classification
FROM compliance_tenants t
CROSS JOIN (VALUES
    ('Allgemein', 'general', 'internal'),
    ('Finanzen', 'finance', 'restricted'),
    ('Personal', 'hr', 'confidential'),
    ('IT', 'it', 'internal'),
    ('Compliance', 'compliance', 'confidential')
) AS ns(name, slug, classification)
WHERE t.slug = 'breakpilot'
ON CONFLICT (tenant_id, slug) DO NOTHING;
