import { supabase } from './supabase';
import { logSecurityEvent, logUserActivity } from './logger';
import type { User, Session } from '@supabase/supabase-js';

export interface AuthContext {
  user: User;
  session: Session;
  isAdmin?: boolean;
  organizationId?: string;
}

/**
 * Server-side authentication verification
 * Validates the auth token from cookies or headers
 */
export async function verifyAuth(request: Request): Promise<AuthContext | null> {
  try {
    // Get auth token from Authorization header or cookies
    const authHeader = request.headers.get('Authorization');
    const cookieHeader = request.headers.get('Cookie');
    
    let accessToken: string | null = null;
    
    // Try Authorization header first
    if (authHeader && authHeader.startsWith('Bearer ')) {
      accessToken = authHeader.substring(7);
    }
    
    // Try cookies if no auth header
    if (!accessToken && cookieHeader) {
      const cookies = parseCookies(cookieHeader);
      accessToken = cookies['sb-access-token'] || cookies['supabase-auth-token'];
    }
    
    if (!accessToken) {
      return null;
    }
    
    // Verify the token with Supabase
    const { data: { user }, error } = await supabase.auth.getUser(accessToken);
    
    if (error || !user) {
      // Log failed authentication attempt
      logSecurityEvent({
        type: 'auth_failure',
        ipAddress: getClientIPFromHeaders(request),
        userAgent: request.headers.get('User-Agent') || undefined,
        severity: 'medium',
        details: { error: error?.message, reason: 'invalid_token' }
      });
      return null;
    }
    
    // Get user's organization
    const { data: userRecord } = await supabase
      .from('users')
      .select('organization_id, role')
      .eq('id', user.id)
      .single();
    
    // Mock session object (since we're doing server-side verification)
    const session: Session = {
      access_token: accessToken,
      refresh_token: '', // Not needed for verification
      expires_in: 3600,
      expires_at: Date.now() / 1000 + 3600,
      token_type: 'bearer',
      user
    };
    
    // Log successful authentication
    logUserActivity({
      action: 'auth_success',
      userId: user.id,
      ipAddress: getClientIPFromHeaders(request),
      userAgent: request.headers.get('User-Agent') || undefined,
      details: { organizationId: userRecord?.organization_id, role: userRecord?.role }
    });

    return {
      user,
      session,
      isAdmin: userRecord?.role === 'admin',
      organizationId: userRecord?.organization_id
    };
  } catch (error) {
    console.error('Auth verification error:', error);
    return null;
  }
}

/**
 * Middleware function to protect routes
 */
export async function requireAuth(request: Request): Promise<AuthContext> {
  const auth = await verifyAuth(request);
  
  if (!auth) {
    logSecurityEvent({
      type: 'access_denied',
      ipAddress: getClientIPFromHeaders(request),
      userAgent: request.headers.get('User-Agent') || undefined,
      severity: 'low',
      details: { reason: 'no_authentication' }
    });
    throw new Error('Authentication required');
  }
  
  return auth;
}

/**
 * Middleware function to require admin access
 */
export async function requireAdmin(request: Request): Promise<AuthContext> {
  const auth = await requireAuth(request);
  
  if (!auth.isAdmin) {
    logSecurityEvent({
      type: 'access_denied',
      userId: auth.user.id,
      ipAddress: getClientIPFromHeaders(request),
      userAgent: request.headers.get('User-Agent') || undefined,
      severity: 'medium',
      details: { reason: 'insufficient_privileges', requiredRole: 'admin' }
    });
    throw new Error('Admin access required');
  }
  
  return auth;
}

/**
 * Check if user has access to a specific organization
 */
export async function requireOrganizationAccess(
  request: Request, 
  organizationId: string
): Promise<AuthContext> {
  const auth = await requireAuth(request);
  
  if (auth.organizationId !== organizationId && !auth.isAdmin) {
    logSecurityEvent({
      type: 'access_denied',
      userId: auth.user.id,
      ipAddress: getClientIPFromHeaders(request),
      userAgent: request.headers.get('User-Agent') || undefined,
      severity: 'high',
      details: { 
        reason: 'organization_access_violation',
        userOrganization: auth.organizationId,
        requestedOrganization: organizationId
      }
    });
    throw new Error('Access denied to this organization');
  }
  
  return auth;
}

/**
 * Generate CSRF token
 */
export function generateCSRFToken(): string {
  return crypto.randomUUID();
}

/**
 * Verify CSRF token
 */
export function verifyCSRFToken(request: Request, sessionToken: string): boolean {
  const submittedToken = request.headers.get('X-CSRF-Token') || 
                        request.headers.get('X-Requested-With');
  
  return submittedToken === sessionToken;
}

/**
 * Rate limiting - simple in-memory implementation
 * For production, use Redis or a proper rate limiting service
 */
const rateLimitStore = new Map<string, { count: number; lastReset: number }>();

export function checkRateLimit(
  identifier: string, 
  maxRequests: number = 10, 
  windowMs: number = 60000
): boolean {
  const now = Date.now();
  const windowStart = now - windowMs;
  
  let entry = rateLimitStore.get(identifier);
  
  if (!entry || entry.lastReset < windowStart) {
    entry = { count: 0, lastReset: now };
    rateLimitStore.set(identifier, entry);
  }
  
  entry.count++;
  
  // Clean up old entries periodically
  if (Math.random() < 0.01) { // 1% chance
    cleanupRateLimit(windowStart);
  }
  
  const isAllowed = entry.count <= maxRequests;
  
  // Log rate limit violations
  if (!isAllowed) {
    logSecurityEvent({
      type: 'rate_limit',
      ipAddress: identifier.includes(':') ? identifier.split(':')[1] : identifier,
      severity: 'medium',
      details: { 
        maxRequests, 
        windowMs, 
        currentCount: entry.count,
        identifier
      }
    });
  }
  
  return isAllowed;
}

function cleanupRateLimit(cutoff: number) {
  for (const [key, entry] of rateLimitStore.entries()) {
    if (entry.lastReset < cutoff) {
      rateLimitStore.delete(key);
    }
  }
}

/**
 * Parse cookies from cookie header
 */
function parseCookies(cookieHeader: string): Record<string, string> {
  const cookies: Record<string, string> = {};
  
  cookieHeader.split(';').forEach(cookie => {
    const [name, ...rest] = cookie.trim().split('=');
    if (name && rest.length > 0) {
      cookies[name] = rest.join('=');
    }
  });
  
  return cookies;
}

/**
 * Create secure response with auth headers
 */
export function createAuthResponse(
  body: string | object, 
  status: number = 200,
  additionalHeaders: Record<string, string> = {}
): Response {
  const headers = {
    'Content-Type': typeof body === 'string' ? 'text/plain' : 'application/json',
    'X-Content-Type-Options': 'nosniff',
    'X-Frame-Options': 'DENY',
    'X-XSS-Protection': '1; mode=block',
    ...additionalHeaders
  };
  
  return new Response(
    typeof body === 'string' ? body : JSON.stringify(body),
    { status, headers }
  );
}

/**
 * Get client IP address for rate limiting
 */
export function getClientIP(request: Request): string {
  return getClientIPFromHeaders(request);
}

/**
 * Helper function to extract IP from headers
 */
function getClientIPFromHeaders(request: Request): string {
  // Try various headers that might contain the real IP
  const forwardedFor = request.headers.get('X-Forwarded-For');
  const realIP = request.headers.get('X-Real-IP');
  const cfConnectingIP = request.headers.get('CF-Connecting-IP');
  
  if (cfConnectingIP) return cfConnectingIP;
  if (realIP) return realIP;
  if (forwardedFor) return forwardedFor.split(',')[0].trim();
  
  // Fallback to connection IP (may not be available in all environments)
  return request.headers.get('X-Client-IP') || 'unknown';
}