-- M4.1 — initial tenant_registry schema.
-- Source of truth: PLATFORM_ARCHITECTURE.md §5c.
-- Forward-only per IMPLEMENTATION_PLAN.md §1.7.

-- =========================================================================
-- enums
-- =========================================================================

CREATE TYPE tenant_status AS ENUM (
    'demo',      -- shared demo tenant; reset nightly; no billing
    'trial',     -- real customer in their N-day evaluation window
    'active',    -- paid; contract or self-serve plan
    'frozen',    -- read-only after cancel / non-payment (30d grace)
    'archived'   -- data export window closed; only audit log retained
);

CREATE TYPE tenant_kind AS ENUM (
    'customer',  -- real paying / trialing customer
    'demo'       -- shared demo tenant; never billed
);

CREATE TYPE idp_kind AS ENUM (
    'oidc',
    'saml'
);

CREATE TYPE tenant_project_status AS ENUM (
    'active',
    'archived'
);

-- =========================================================================
-- tenants — the root entity. tenants.id ↔ Keycloak org_id 1:1.
-- =========================================================================

CREATE TABLE tenants (
    id               UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    slug             TEXT UNIQUE NOT NULL CHECK (slug ~ '^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$'),
    name             TEXT NOT NULL,
    status           tenant_status NOT NULL DEFAULT 'trial',
    kind             tenant_kind   NOT NULL DEFAULT 'customer',
    plan             TEXT NOT NULL DEFAULT 'starter',

    -- External system references (one-to-one per §5c "Links")
    erp_customer_id  TEXT UNIQUE,
    stripe_cust_id   TEXT UNIQUE,

    -- Lifecycle dates
    trial_ends_at    TIMESTAMPTZ,
    contract_start   DATE,
    contract_end     DATE,

    -- CRM ownership (ERPNext sales_owner equivalent)
    sales_owner      TEXT,

    created_at       TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at       TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX tenants_status_idx ON tenants (status);
CREATE INDEX tenants_kind_idx   ON tenants (kind);
CREATE INDEX tenants_trial_ends_idx ON tenants (trial_ends_at) WHERE trial_ends_at IS NOT NULL;

-- =========================================================================
-- tenant_projects — OPTIONAL sub-tenancy (GCP-Project-style).
-- Customers without need operate as a single implicit "default" project.
-- Products opt in via manifest.supports_projects=true.
-- =========================================================================

CREATE TABLE tenant_projects (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
    name        TEXT NOT NULL,
    slug        TEXT NOT NULL CHECK (slug ~ '^[a-z0-9][a-z0-9-]{0,38}[a-z0-9]$'),
    status      tenant_project_status NOT NULL DEFAULT 'active',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (tenant_id, slug)
);

CREATE INDEX tenant_projects_tenant_idx ON tenant_projects (tenant_id);

-- =========================================================================
-- tenant_products — entitlement matrix: which tenant has which product.
-- config holds product-specific knobs (litellm_url, max_seats, modules_enabled…).
-- =========================================================================

CREATE TABLE tenant_products (
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
    product     TEXT NOT NULL,
    enabled     BOOLEAN NOT NULL DEFAULT TRUE,
    config      JSONB NOT NULL DEFAULT '{}'::jsonb,
    expires_at  TIMESTAMPTZ,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (tenant_id, product)
);

CREATE INDEX tenant_products_product_idx ON tenant_products (product) WHERE enabled = TRUE;

-- =========================================================================
-- tenant_idp_config — external identity provider per tenant (enterprise SSO).
-- metadata holds OIDC discovery URL + client_id, or SAML cert + entity_id.
-- =========================================================================

CREATE TABLE tenant_idp_config (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id   UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
    type        idp_kind NOT NULL,
    metadata    JSONB NOT NULL DEFAULT '{}'::jsonb,
    verified    BOOLEAN NOT NULL DEFAULT FALSE,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (tenant_id, type)
);

CREATE INDEX tenant_idp_config_tenant_idx ON tenant_idp_config (tenant_id);

-- =========================================================================
-- api_keys — portal-owned. Single source of truth across all products.
-- hash is bcrypt/argon2 of the raw key; the plaintext is shown ONCE on create.
-- =========================================================================

CREATE TABLE api_keys (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id     UUID NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
    product       TEXT,                                   -- nullable = applies to all products
    name          TEXT NOT NULL,                          -- human-readable label
    scopes        TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
    hash          TEXT NOT NULL,                          -- argon2id encoded hash
    prefix        TEXT NOT NULL,                          -- first 8 chars of the raw key, for UI display
    created_by    TEXT,                                   -- Keycloak user_id
    last_used_at  TIMESTAMPTZ,
    revoked_at    TIMESTAMPTZ,
    created_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE (tenant_id, name)
);

CREATE INDEX api_keys_tenant_idx ON api_keys (tenant_id) WHERE revoked_at IS NULL;
CREATE INDEX api_keys_prefix_idx ON api_keys (prefix);

-- =========================================================================
-- audit_log — every state-changing action across portal + products.
-- Retraced-compatible shape (PRODUCT_INTEGRATION_SPEC.md §8.4) so we can
-- swap implementations without changing producers.
-- =========================================================================

CREATE TABLE audit_log (
    id           BIGSERIAL PRIMARY KEY,
    tenant_id    UUID REFERENCES tenants(id) ON DELETE SET NULL,
    project_id   UUID REFERENCES tenant_projects(id) ON DELETE SET NULL,
    actor_id     TEXT,
    actor_name   TEXT,
    actor_type   TEXT,           -- user | service | system
    action       TEXT NOT NULL,
    target_id    TEXT,
    target_type  TEXT,
    target_name  TEXT,
    product      TEXT,           -- which product emitted this (NULL = portal/tenant-registry)
    metadata     JSONB NOT NULL DEFAULT '{}'::jsonb,
    source_ip    INET,
    user_agent   TEXT,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX audit_log_tenant_idx        ON audit_log (tenant_id, created_at DESC);
CREATE INDEX audit_log_product_idx       ON audit_log (product, created_at DESC) WHERE product IS NOT NULL;
CREATE INDEX audit_log_actor_idx         ON audit_log (actor_id, created_at DESC) WHERE actor_id IS NOT NULL;
CREATE INDEX audit_log_action_idx        ON audit_log (action);
CREATE INDEX audit_log_tenant_action_idx ON audit_log (tenant_id, action, created_at DESC);

-- =========================================================================
-- update timestamp trigger — applied to every table with an updated_at.
-- =========================================================================

CREATE OR REPLACE FUNCTION touch_updated_at() RETURNS trigger AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tenants_touch_updated_at
    BEFORE UPDATE ON tenants
    FOR EACH ROW EXECUTE FUNCTION touch_updated_at();

CREATE TRIGGER tenant_projects_touch_updated_at
    BEFORE UPDATE ON tenant_projects
    FOR EACH ROW EXECUTE FUNCTION touch_updated_at();

CREATE TRIGGER tenant_products_touch_updated_at
    BEFORE UPDATE ON tenant_products
    FOR EACH ROW EXECUTE FUNCTION touch_updated_at();

CREATE TRIGGER tenant_idp_config_touch_updated_at
    BEFORE UPDATE ON tenant_idp_config
    FOR EACH ROW EXECUTE FUNCTION touch_updated_at();
