/**
 * AuthGuard - Authentication guard for <%= it.authConfig.provider %> provider
 *
 * Auto-generated by Apso CLI from .apsorc auth configuration
 * Generated at: <%= it.generatedAt %>
 *
 * This guard:
 * - Validates session tokens from cookies/headers
 * - Loads user and organization context
 * - Attaches normalized AuthContext to requests
 *
 * DO NOT EDIT - Changes will be overwritten on next scaffold
 */
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

// Entity imports (from sibling autogen folders)
import { <%= it.authConfig.sessionEntity %> } from '../<%= it.authConfig.sessionEntity %>/<%= it.authConfig.sessionEntity %>.entity';
import { <%= it.authConfig.userEntity %> } from '../<%= it.authConfig.userEntity %>/<%= it.authConfig.userEntity %>.entity';
<% if (it.authConfig.accountUserEntity) { %>
import { <%= it.authConfig.accountUserEntity %> } from '../<%= it.authConfig.accountUserEntity %>/<%= it.authConfig.accountUserEntity %>.entity';
<% } %>

/**
 * Metadata key for marking routes as public (no auth required)
 */
export const IS_PUBLIC_KEY = 'isPublic';

/**
 * Decorator to mark a route as public (skips authentication)
 * Usage: @Public()
 */
export const Public =
  () => (target: any, key?: string, descriptor?: PropertyDescriptor) => {
    Reflect.defineMetadata(IS_PUBLIC_KEY, true, descriptor?.value || target);
  };

/**
 * Normalized auth context attached to authenticated requests.
 * This is what scopeBy and RBAC consume.
 */
export interface AuthContext {
  userId?: string;
  email?: string;
  workspaceId?: string;
  organizationId?: string;
  roles: string[];
  user?: <%= it.authConfig.userEntity %>;
  session?: <%= it.authConfig.sessionEntity %>;
}

/**
 * Extended request type with auth context
 */
export interface AuthenticatedRequest extends Request {
  auth: AuthContext;
  user: <%= it.authConfig.userEntity %>;
  session: <%= it.authConfig.sessionEntity %>;
  // Legacy compatibility aliases
  accountId?: string;
  accountRole?: string;
  organizationId?: string;
}

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    @InjectRepository(<%= it.authConfig.sessionEntity %>)
    private sessionRepository: Repository<<%= it.authConfig.sessionEntity %>>,
    @InjectRepository(<%= it.authConfig.userEntity %>)
    private userRepository: Repository<<%= it.authConfig.userEntity %>>,
<% if (it.authConfig.accountUserEntity) { %>
    @InjectRepository(<%= it.authConfig.accountUserEntity %>)
    private accountUserRepository: Repository<<%= it.authConfig.accountUserEntity %>>,
<% } %>
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // Check if route is marked as public
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const token = this.extractToken(request);

    if (!token) {
      throw new UnauthorizedException('No session token provided');
    }

    try {
      // Find session by token
      const sessionRecord = await this.sessionRepository.findOne({
        where: { token },
      });

      if (!sessionRecord) {
        throw new UnauthorizedException('Invalid session token');
      }

      // Check if session is expired
      if (new Date(sessionRecord.expiresAt) < new Date()) {
        throw new UnauthorizedException('Session expired');
      }

      // Get user from session
      const user = await this.userRepository.findOne({
        where: { id: sessionRecord.userId },
      });

      if (!user) {
        throw new UnauthorizedException('User not found');
      }

<% if (it.authConfig.accountUserEntity) { %>
      // Get user's organization through <%= it.authConfig.accountUserEntity %> junction
      const accountUser = await this.accountUserRepository.findOne({
        where: { userId: user.id },
      });

      if (!accountUser) {
        throw new UnauthorizedException('User not associated with any organization');
      }

      const organizationId = accountUser.<%= it.authConfig.organizationField %>;
      const role = accountUser.<%= it.authConfig.roleField %> || 'member';
<% } else { %>
      // No account user entity configured - using user-level organization
      const organizationId = (user as any).<%= it.authConfig.organizationField %> || undefined;
      const role = (user as any).<%= it.authConfig.roleField %> || 'member';
<% } %>

      // Build normalized AuthContext
      const authContext: AuthContext = {
        userId: user.id,
        email: user.email,
        workspaceId: organizationId,
        organizationId: organizationId,
        roles: [role],
        user: user,
        session: sessionRecord,
      };

      // Attach to request
      (request as any).auth = authContext;
      (request as any).user = user;
      (request as any).session = sessionRecord;

      // Legacy compatibility - attach directly to request root
      (request as any).accountId = organizationId;
      (request as any).accountRole = role;
      (request as any).organizationId = organizationId;

      return true;
    } catch (error) {
      if (error instanceof UnauthorizedException) {
        throw error;
      }
      console.error('Auth guard error:', error);
      throw new UnauthorizedException('Authentication failed');
    }
  }

  /**
   * Extract session token from request
   * Checks: Authorization header (Bearer), cookies, X-Session-Token header
   */
  private extractToken(request: any): string | undefined {
    // Check Authorization header first (Bearer token)
    const authHeader = request.headers.authorization;
    if (authHeader && authHeader.startsWith('Bearer ')) {
      return authHeader.substring(7);
    }

    // Check for session token in cookies
    const cookies = request.cookies || {};
<% if (it.authConfig.cookiePrefix) { %>
    const cookieName = '<%= it.authConfig.cookiePrefix %>.session_token';
    const sessionToken =
      cookies[cookieName] ||
      cookies['better-auth.session_token'] ||
      cookies['session_token'];
<% } else { %>
    const sessionToken =
      cookies['better-auth.session_token'] ||
      cookies['session_token'];
<% } %>

    if (sessionToken) {
      return sessionToken;
    }

    // Check X-Session-Token header
    return request.headers['x-session-token'];
  }
}

/**
 * Helper function to get AuthContext from request
 * Use this in controllers/services to access the authenticated context
 */
export function getAuthContext(request: any): AuthContext | undefined {
  return request?.auth;
}

/**
 * Helper function to require AuthContext (throws if not present)
 */
export function requireAuthContext(request: any): AuthContext {
  const ctx = getAuthContext(request);
  if (!ctx) {
    throw new UnauthorizedException('Authentication required');
  }
  return ctx;
}
