-- ============================================================================
-- SCHEMA: auth
-- TABLE: tokens
-- DESCRIPTION: Tokens de refresh, reset password, verificacion email, etc.
-- VERSION: 1.0.0
-- CREATED: 2026-01-10
-- ============================================================================

-- Enum para tipo de token
DO $$ BEGIN
    CREATE TYPE auth.token_type AS ENUM (
        'refresh',          -- Refresh token para JWT
        'password_reset',   -- Reset de password
        'email_verify',     -- Verificacion de email
        'phone_verify',     -- Verificacion de telefono
        'api_key',          -- API key para integraciones
        'invitation'        -- Invitacion a unirse a tenant
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para estado de token
DO $$ BEGIN
    CREATE TYPE auth.token_status AS ENUM (
        'active',       -- Token activo y usable
        'used',         -- Ya fue usado (one-time tokens)
        'expired',      -- Expirado por tiempo
        'revoked'       -- Revocado manualmente
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de Tokens
CREATE TABLE IF NOT EXISTS auth.tokens (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users.users(id) ON DELETE CASCADE,  -- NULL para invitations pre-user
    tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,

    -- Token info
    token_type auth.token_type NOT NULL,
    token_hash VARCHAR(255) NOT NULL,  -- Hash del token

    -- Estado
    status auth.token_status NOT NULL DEFAULT 'active',

    -- Metadata (flexible por tipo)
    metadata JSONB DEFAULT '{}'::JSONB,
    -- Para invitations: { "email": "...", "role_id": "..." }
    -- Para api_key: { "name": "...", "scopes": [...] }

    -- Timestamps
    expires_at TIMESTAMPTZ NOT NULL,
    used_at TIMESTAMPTZ,
    revoked_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE auth.tokens IS
'Tokens para diversos propositos: refresh, reset, verificacion, API keys, invitaciones';

COMMENT ON COLUMN auth.tokens.metadata IS
'Metadata especifica por tipo de token (email para invitations, scopes para API keys, etc.)';

-- Indices
CREATE INDEX IF NOT EXISTS idx_tokens_user
    ON auth.tokens(user_id) WHERE user_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_tokens_tenant
    ON auth.tokens(tenant_id);

CREATE INDEX IF NOT EXISTS idx_tokens_hash
    ON auth.tokens(token_hash);

CREATE INDEX IF NOT EXISTS idx_tokens_type_status
    ON auth.tokens(token_type, status) WHERE status = 'active';

CREATE INDEX IF NOT EXISTS idx_tokens_expires
    ON auth.tokens(expires_at) WHERE status = 'active';

-- Indice para buscar invitations por email
CREATE INDEX IF NOT EXISTS idx_tokens_invitation_email
    ON auth.tokens((metadata->>'email'))
    WHERE token_type = 'invitation' AND status = 'active';

-- Funcion para crear token de reset de password
CREATE OR REPLACE FUNCTION auth.create_password_reset_token(
    p_user_id UUID,
    p_tenant_id UUID,
    p_token_hash VARCHAR(255),
    p_expires_in INTERVAL DEFAULT '1 hour'
)
RETURNS UUID AS $$
DECLARE
    v_token_id UUID;
BEGIN
    -- Revocar tokens anteriores de reset
    UPDATE auth.tokens
    SET status = 'revoked', revoked_at = NOW()
    WHERE user_id = p_user_id
      AND token_type = 'password_reset'
      AND status = 'active';

    -- Crear nuevo token
    INSERT INTO auth.tokens (user_id, tenant_id, token_type, token_hash, expires_at)
    VALUES (p_user_id, p_tenant_id, 'password_reset', p_token_hash, NOW() + p_expires_in)
    RETURNING id INTO v_token_id;

    RETURN v_token_id;
END;
$$ LANGUAGE plpgsql;

-- Funcion para validar y usar token
CREATE OR REPLACE FUNCTION auth.use_token(
    p_token_hash VARCHAR(255),
    p_token_type auth.token_type
)
RETURNS TABLE (
    user_id UUID,
    tenant_id UUID,
    metadata JSONB,
    is_valid BOOLEAN
) AS $$
DECLARE
    v_token auth.tokens%ROWTYPE;
BEGIN
    -- Buscar token activo
    SELECT * INTO v_token
    FROM auth.tokens t
    WHERE t.token_hash = p_token_hash
      AND t.token_type = p_token_type
      AND t.status = 'active'
      AND t.expires_at > NOW();

    IF NOT FOUND THEN
        RETURN QUERY SELECT NULL::UUID, NULL::UUID, NULL::JSONB, FALSE;
        RETURN;
    END IF;

    -- Marcar como usado (solo para one-time tokens)
    IF p_token_type IN ('password_reset', 'email_verify', 'phone_verify', 'invitation') THEN
        UPDATE auth.tokens
        SET status = 'used', used_at = NOW()
        WHERE id = v_token.id;
    END IF;

    RETURN QUERY SELECT v_token.user_id, v_token.tenant_id, v_token.metadata, TRUE;
END;
$$ LANGUAGE plpgsql;

-- Funcion para limpiar tokens expirados
CREATE OR REPLACE FUNCTION auth.cleanup_expired_tokens()
RETURNS INTEGER AS $$
DECLARE
    updated_count INTEGER;
BEGIN
    UPDATE auth.tokens
    SET status = 'expired'
    WHERE status = 'active'
      AND expires_at < NOW();

    GET DIAGNOSTICS updated_count = ROW_COUNT;

    -- Eliminar tokens muy antiguos (> 30 dias)
    DELETE FROM auth.tokens
    WHERE status IN ('used', 'expired', 'revoked')
      AND created_at < NOW() - INTERVAL '30 days';

    RETURN updated_count;
END;
$$ LANGUAGE plpgsql;

-- RLS Policy para multi-tenancy
ALTER TABLE auth.tokens ENABLE ROW LEVEL SECURITY;

CREATE POLICY tokens_tenant_isolation ON auth.tokens
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE, DELETE ON auth.tokens TO trading_app;
GRANT SELECT ON auth.tokens TO trading_readonly;
