/**
 * Unified Authentication Module
 * This is the SINGLE source of truth for all authentication in the application.
 * All auth-related functions should be imported from this file.
 */

import { createSupabaseServerClient, createSupabaseServerClientFromRequest } from './supabase-ssr';
import { logSecurityEvent, logUserActivity } from './logger';
import type { AstroCookies } from 'astro';
import type { User, Session } from '@supabase/supabase-js';

// Custom error class for auth-related errors
export class AuthError extends Error {
  constructor(
    message: string,
    public code: 'NO_SESSION' | 'INVALID_TOKEN' | 'NO_PERMISSION' | 'EXPIRED' | 'NO_ORGANIZATION',
    public statusCode: number = 401
  ) {
    super(message);
    this.name = 'AuthError';
  }
}

// Auth context interface
export interface AuthContext {
  user: User;
  session: Session;
  isAdmin: boolean;
  isSuperAdmin: boolean;
  organizationId: string | null;
}

// Type guard for authentication
export function isAuthenticated(auth: AuthContext | null): auth is AuthContext {
  return auth !== null;
}

/**
 * Universal auth verification function
 * Works with both Request objects (API routes) and AstroCookies (Astro pages)
 */
export async function verifyAuth(requestOrCookies: Request | AstroCookies): Promise<AuthContext | null> {
  try {
    // Create appropriate Supabase client based on input type
    const supabase = requestOrCookies instanceof Request 
      ? createSupabaseServerClientFromRequest(requestOrCookies)
      : createSupabaseServerClient(requestOrCookies);
    
    // Get session from cookies
    const { data: { session }, error } = await supabase.auth.getSession();
    
    if (error || !session) {
      // Try Authorization header as fallback (only for Request objects)
      if (requestOrCookies instanceof Request) {
        const authHeader = requestOrCookies.headers.get('Authorization');
        if (authHeader && authHeader.startsWith('Bearer ')) {
          const accessToken = authHeader.substring(7);
          const { data: { user }, error: tokenError } = await supabase.auth.getUser(accessToken);
          
          if (tokenError || !user) {
            logSecurityEvent({
              type: 'auth_failure',
              ipAddress: getClientIP(requestOrCookies),
              userAgent: requestOrCookies.headers.get('User-Agent') || undefined,
              severity: 'medium',
              details: { error: tokenError?.message, reason: 'invalid_bearer_token' }
            });
            return null;
          }
          
          // Create a minimal session for bearer token auth
          return await buildAuthContext(user, accessToken, supabase, requestOrCookies);
        }
      }
      
      // No valid session or token found
      return null;
    }
    
    // Build and return auth context
    return await buildAuthContext(session.user, session.access_token, supabase, requestOrCookies);
  } catch (error) {
    console.error('[Auth] Verification error:', error);
    return null;
  }
}

/**
 * Build auth context with user data from database
 */
async function buildAuthContext(
  user: User,
  accessToken: string,
  supabaseClient: any,
  requestOrCookies: Request | AstroCookies
): Promise<AuthContext> {
  // Get additional user data from database
  const { data: userRecord, error: dbError } = await supabaseClient
    .from('users')
    .select('role, organization_id')
    .eq('id', user.id)
    .single();

  if (dbError) {
    console.error('[Auth] Database user lookup failed:', dbError.message);
  }

  // Log successful authentication
  if (requestOrCookies instanceof Request) {
    logUserActivity({
      action: 'auth_success',
      userId: user.id,
      ipAddress: getClientIP(requestOrCookies),
      userAgent: requestOrCookies.headers.get('User-Agent') || undefined,
      details: { organizationId: userRecord?.organization_id, role: userRecord?.role }
    });
  }

  return {
    user,
    session: {
      access_token: accessToken,
      refresh_token: '',
      expires_in: 3600,
      expires_at: Date.now() / 1000 + 3600,
      token_type: 'bearer',
      user
    } as Session,
    isAdmin: userRecord?.role === 'admin' || false,
    isSuperAdmin: false, // Super admin logic can be added later if needed
    organizationId: userRecord?.organization_id || null
  };
}

/**
 * Require authentication - throws if not authenticated
 */
export async function requireAuth(requestOrCookies: Request | AstroCookies): Promise<AuthContext> {
  const auth = await verifyAuth(requestOrCookies);
  
  if (!auth) {
    if (requestOrCookies instanceof Request) {
      logSecurityEvent({
        type: 'access_denied',
        ipAddress: getClientIP(requestOrCookies),
        userAgent: requestOrCookies.headers.get('User-Agent') || undefined,
        severity: 'low',
        details: { reason: 'no_authentication' }
      });
    }
    throw new AuthError('Authentication required', 'NO_SESSION');
  }
  
  return auth;
}

/**
 * Require admin access - throws if not admin
 */
export async function requireAdmin(requestOrCookies: Request | AstroCookies): Promise<AuthContext> {
  const auth = await requireAuth(requestOrCookies);
  
  if (!auth.isAdmin) {
    if (requestOrCookies instanceof Request) {
      logSecurityEvent({
        type: 'access_denied',
        userId: auth.user.id,
        ipAddress: getClientIP(requestOrCookies),
        userAgent: requestOrCookies.headers.get('User-Agent') || undefined,
        severity: 'medium',
        details: { reason: 'insufficient_privileges', requiredRole: 'admin' }
      });
    }
    throw new AuthError('Admin access required', 'NO_PERMISSION', 403);
  }
  
  return auth;
}

/**
 * Require super admin access - throws if not super admin
 */
export async function requireSuperAdmin(requestOrCookies: Request | AstroCookies): Promise<AuthContext> {
  const auth = await requireAuth(requestOrCookies);
  
  if (!auth.isSuperAdmin) {
    if (requestOrCookies instanceof Request) {
      logSecurityEvent({
        type: 'access_denied',
        userId: auth.user.id,
        ipAddress: getClientIP(requestOrCookies),
        userAgent: requestOrCookies.headers.get('User-Agent') || undefined,
        severity: 'high',
        details: { reason: 'insufficient_privileges', requiredRole: 'super_admin' }
      });
    }
    throw new AuthError('Super admin access required', 'NO_PERMISSION', 403);
  }
  
  return auth;
}

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

/**
 * Middleware wrapper for auth-protected routes
 */
export async function withAuth<T>(
  requestOrCookies: Request | AstroCookies,
  handler: (auth: AuthContext) => T | Promise<T>
): Promise<T> {
  const auth = await verifyAuth(requestOrCookies);
  if (!auth) {
    throw new AuthError('Not authenticated', 'NO_SESSION');
  }
  return handler(auth);
}

/**
 * Generate CSRF token for forms
 */
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);
    }
  }
}

/**
 * Get client IP address from request
 */
export function getClientIP(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
  return request.headers.get('X-Client-IP') || 'unknown';
}

/**
 * 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 }
  );
}

/**
 * Debug utilities (only in development)
 */
export const authDebug = {
  logCookies: (request: Request) => {
    if (process.env.NODE_ENV !== 'development') return;
    const cookieHeader = request.headers.get('Cookie');
    console.log('[Auth Debug] Cookies:', cookieHeader);
  },
  
  logSession: (session: Session | null) => {
    if (process.env.NODE_ENV !== 'development') return;
    console.log('[Auth Debug] Session:', session ? {
      user_id: session.user.id,
      expires_at: new Date(session.expires_at! * 1000).toISOString()
    } : 'No session');
  }
};

// Re-export frequently used functions with simpler names (backwards compatibility)
export {
  verifyAuth as verifyAuthSimple,
  requireAdmin as requireAdminSimple,
  requireSuperAdmin as requireSuperAdminSimple
};