-- ============================================================================
-- SCHEMA: auth
-- TABLE: sessions
-- DESCRIPTION: Gestion de sesiones de usuario con tracking de dispositivos
-- VERSION: 1.0.0
-- CREATED: 2026-01-10
-- ============================================================================

-- Crear schema si no existe
CREATE SCHEMA IF NOT EXISTS auth;

-- Enum para estado de sesion
DO $$ BEGIN
    CREATE TYPE auth.session_status AS ENUM (
        'active',       -- Sesion activa
        'expired',      -- Expirada por tiempo
        'revoked'       -- Revocada manualmente (logout)
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Enum para tipo de dispositivo
DO $$ BEGIN
    CREATE TYPE auth.device_type AS ENUM (
        'desktop',
        'mobile',
        'tablet',
        'unknown'
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de Sesiones
CREATE TABLE IF NOT EXISTS auth.sessions (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,

    -- Token hash (para validacion)
    token_hash VARCHAR(255) NOT NULL,

    -- Informacion del dispositivo
    device_type auth.device_type DEFAULT 'unknown',
    device_name VARCHAR(255),
    browser VARCHAR(100),
    browser_version VARCHAR(50),
    os VARCHAR(100),
    os_version VARCHAR(50),

    -- Ubicacion
    ip_address INET,
    user_agent TEXT,

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

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

COMMENT ON TABLE auth.sessions IS
'Sesiones activas de usuarios con informacion de dispositivo para seguridad';

COMMENT ON COLUMN auth.sessions.token_hash IS
'Hash SHA256 del refresh token para validacion sin almacenar el token';

-- Indices
CREATE INDEX IF NOT EXISTS idx_sessions_user
    ON auth.sessions(user_id);

CREATE INDEX IF NOT EXISTS idx_sessions_tenant
    ON auth.sessions(tenant_id);

CREATE INDEX IF NOT EXISTS idx_sessions_token_hash
    ON auth.sessions(token_hash);

CREATE INDEX IF NOT EXISTS idx_sessions_status
    ON auth.sessions(status) WHERE status = 'active';

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

CREATE INDEX IF NOT EXISTS idx_sessions_last_active
    ON auth.sessions(last_active_at DESC);

-- Funcion para limpiar sesiones expiradas
CREATE OR REPLACE FUNCTION auth.cleanup_expired_sessions()
RETURNS INTEGER AS $$
DECLARE
    deleted_count INTEGER;
BEGIN
    UPDATE auth.sessions
    SET status = 'expired'
    WHERE status = 'active'
      AND expires_at < NOW();

    GET DIAGNOSTICS deleted_count = ROW_COUNT;

    -- Eliminar sesiones muy antiguas (> 90 dias)
    DELETE FROM auth.sessions
    WHERE (status = 'expired' OR status = 'revoked')
      AND created_at < NOW() - INTERVAL '90 days';

    RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION auth.cleanup_expired_sessions() IS
'Limpia sesiones expiradas y elimina registros muy antiguos. Ejecutar periodicamente.';

-- Funcion para revocar todas las sesiones de un usuario
CREATE OR REPLACE FUNCTION auth.revoke_all_user_sessions(
    p_user_id UUID,
    p_except_session_id UUID DEFAULT NULL
)
RETURNS INTEGER AS $$
DECLARE
    revoked_count INTEGER;
BEGIN
    UPDATE auth.sessions
    SET status = 'revoked',
        revoked_at = NOW()
    WHERE user_id = p_user_id
      AND status = 'active'
      AND (p_except_session_id IS NULL OR id != p_except_session_id);

    GET DIAGNOSTICS revoked_count = ROW_COUNT;
    RETURN revoked_count;
END;
$$ LANGUAGE plpgsql;

COMMENT ON FUNCTION auth.revoke_all_user_sessions IS
'Revoca todas las sesiones activas de un usuario, opcionalmente excepto una sesion especifica';

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

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

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