-- =====================================================
-- CRITICAL SECURITY FIXES - Priority 1 & 2
-- =====================================================

-- =====================================================
-- 1. PREVENT PRIVILEGE ESCALATION
-- =====================================================

-- Function to prevent unauthorized superuser role assignment
CREATE OR REPLACE FUNCTION public.prevent_superuser_escalation()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
  -- If trying to grant superuser role
  IF NEW.role = 'superuser' THEN
    -- Only existing superusers can grant superuser role
    IF NOT EXISTS (
      SELECT 1 FROM public.user_roles
      WHERE user_id = auth.uid()
        AND role = 'superuser'
    ) THEN
      RAISE EXCEPTION 'Only superusers can grant the superuser role';
    END IF;
  END IF;
  
  RETURN NEW;
END;
$$;

-- Apply trigger to user_roles INSERT
DROP TRIGGER IF EXISTS enforce_superuser_escalation_prevention ON public.user_roles;
CREATE TRIGGER enforce_superuser_escalation_prevention
  BEFORE INSERT ON public.user_roles
  FOR EACH ROW
  EXECUTE FUNCTION public.prevent_superuser_escalation();

-- Function to prevent unauthorized modification of superuser roles
CREATE OR REPLACE FUNCTION public.prevent_superuser_role_removal()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
  -- If trying to delete a superuser role
  IF OLD.role = 'superuser' THEN
    -- Only existing superusers can remove superuser roles
    IF NOT EXISTS (
      SELECT 1 FROM public.user_roles
      WHERE user_id = auth.uid()
        AND role = 'superuser'
    ) THEN
      RAISE EXCEPTION 'Only superusers can remove the superuser role';
    END IF;
  END IF;
  
  RETURN OLD;
END;
$$;

-- Apply trigger to user_roles DELETE
DROP TRIGGER IF EXISTS enforce_superuser_removal_prevention ON public.user_roles;
CREATE TRIGGER enforce_superuser_removal_prevention
  BEFORE DELETE ON public.user_roles
  FOR EACH ROW
  EXECUTE FUNCTION public.prevent_superuser_role_removal();

-- Function to audit all role changes
CREATE OR REPLACE FUNCTION public.audit_role_changes()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
  IF TG_OP = 'INSERT' THEN
    INSERT INTO public.admin_audit_log (
      admin_user_id,
      target_user_id,
      action,
      details
    ) VALUES (
      auth.uid(),
      NEW.user_id,
      'role_granted',
      jsonb_build_object(
        'role', NEW.role,
        'timestamp', now()
      )
    );
  ELSIF TG_OP = 'DELETE' THEN
    INSERT INTO public.admin_audit_log (
      admin_user_id,
      target_user_id,
      action,
      details
    ) VALUES (
      auth.uid(),
      OLD.user_id,
      'role_revoked',
      jsonb_build_object(
        'role', OLD.role,
        'timestamp', now()
      )
    );
  END IF;
  
  RETURN COALESCE(NEW, OLD);
END;
$$;

-- Apply trigger to user_roles
DROP TRIGGER IF EXISTS audit_role_changes_trigger ON public.user_roles;
CREATE TRIGGER audit_role_changes_trigger
  AFTER INSERT OR DELETE ON public.user_roles
  FOR EACH ROW
  EXECUTE FUNCTION public.audit_role_changes();

-- =====================================================
-- 2. RESTRICT PUBLIC PROFILE ACCESS
-- =====================================================

-- Remove overly permissive policy
DROP POLICY IF EXISTS "Public can view basic profile info only" ON public.profiles;

-- New policy: Authenticated users can view profiles
CREATE POLICY "Authenticated users can view profiles"
ON public.profiles
FOR SELECT
TO authenticated
USING (
  -- Users can view their own profile completely
  (auth.uid() = user_id)
  OR
  -- Moderators can view all profiles
  is_moderator(auth.uid())
  OR
  -- Others can only view public, non-banned profiles
  (privacy_level = 'public' AND NOT banned)
);

-- =====================================================
-- 3. SESSION SECURITY ENHANCEMENTS
-- =====================================================

-- Function to hash IP addresses for privacy
CREATE OR REPLACE FUNCTION public.hash_ip_address(ip_text text)
RETURNS text
LANGUAGE plpgsql
IMMUTABLE
AS $$
BEGIN
  -- Use SHA256 hash with salt
  RETURN encode(
    digest(ip_text || 'thrillwiki_ip_salt_2025', 'sha256'),
    'hex'
  );
END;
$$;

-- Add hashed IP column if not exists
ALTER TABLE public.user_sessions
ADD COLUMN IF NOT EXISTS ip_address_hash text;

-- Update existing records (hash current IPs)
UPDATE public.user_sessions
SET ip_address_hash = public.hash_ip_address(host(ip_address)::text)
WHERE ip_address IS NOT NULL AND ip_address_hash IS NULL;

-- Function to clean up expired sessions
CREATE OR REPLACE FUNCTION public.cleanup_expired_sessions()
RETURNS void
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
  DELETE FROM public.user_sessions
  WHERE expires_at < now();
END;
$$;

-- Allow users to delete their own sessions (for revocation)
DROP POLICY IF EXISTS "Users can delete their own sessions" ON public.user_sessions;
CREATE POLICY "Users can delete their own sessions"
ON public.user_sessions
FOR DELETE
TO authenticated
USING (auth.uid() = user_id);

-- Allow users to view their own sessions
DROP POLICY IF EXISTS "Users can view their own sessions" ON public.user_sessions;
CREATE POLICY "Users can view their own sessions"
ON public.user_sessions
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);