/**
 * Blazor Concurrency Analyzer
 *
 * Detects potential DbContext concurrency issues in Blazor Server applications.
 * Identifies patterns that lead to "A second operation was started" exceptions.
 *
 * @module blazor-concurrency-analyzer
 */

import { countIssues } from '../shared/index.js';

/**
 * @typedef {Object} ConcurrencyIssue
 * @property {'error' | 'warning' | 'info'} type
 * @property {string} message
 * @property {string} [suggestion]
 * @property {string} file
 * @property {number} [line]
 * @property {string} [code]
 */

// Patterns that indicate scoped DbContext injection
const SCOPED_DBCONTEXT_PATTERNS = [
  /private readonly (\w+DbContext)\s+_\w+/g,
  /\[Inject\]\s*(?:private|public)\s+(\w+DbContext)/g,
  /private readonly I(\w+)Repository\s+_\w+/g,
];

// Patterns that indicate background operations
const BACKGROUND_OPERATION_PATTERNS = [
  /Task\.Run\s*\(/,
  /Task\.Factory\.StartNew\s*\(/,
  /Parallel\.For/,
  /Parallel\.ForEach/,
  /\.ConfigureAwait\(false\)/,
  /_ = \w+Async\(/,  // Fire-and-forget pattern
  /ThreadPool\.QueueUserWorkItem/,
];

// Patterns that indicate factory usage (GOOD)
const FACTORY_PATTERNS = [
  /IDbContextFactory<\w+>/,
  /IDbContextFactory\s*<\s*\w+\s*>/,
  /_factory\.CreateDbContext/,
  /_contextFactory\.CreateDbContext/,
  /await using var \w+ = .*CreateDbContext/,
];

// Hangfire patterns
const HANGFIRE_PATTERNS = [
  /BackgroundJob\.Enqueue/,
  /BackgroundJob\.Schedule/,
  /RecurringJob\.AddOrUpdate/,
  /\[AutomaticRetry\]/,
];

/**
 * Checks for scoped DbContext used in background operations.
 *
 * @param {string} content - File content
 * @param {string} filePath - Path to the file
 * @returns {ConcurrencyIssue[]}
 */
export function checkScopedDbContextInBackground(content, filePath) {
  const issues = [];
  const lines = content.split('\n');

  // First, check if file has scoped DbContext
  let hasScopedDbContext = false;
  let dbContextLine = 0;

  lines.forEach((line, index) => {
    SCOPED_DBCONTEXT_PATTERNS.forEach((pattern) => {
      if (pattern.test(line)) {
        hasScopedDbContext = true;
        dbContextLine = index + 1;
      }
      pattern.lastIndex = 0;
    });
  });

  if (!hasScopedDbContext) {
    return issues;
  }

  // Check if file has factory pattern (GOOD - no issues)
  const hasFactory = FACTORY_PATTERNS.some((pattern) => pattern.test(content));

  if (hasFactory) {
    return issues; // Using factory pattern correctly
  }

  // Check for background operations
  lines.forEach((line, index) => {
    BACKGROUND_OPERATION_PATTERNS.forEach((pattern) => {
      if (pattern.test(line)) {
        issues.push({
          type: 'error',
          message: 'Operacao background com DbContext scoped detectada',
          suggestion:
            'Use IDbContextFactory<T> ou Repository Factory pattern para operacoes background. Ver framework/standards/backend/database/ef-core.md',
          file: filePath,
          line: index + 1,
          code: line.trim(),
        });
      }
    });
  });

  return issues;
}

/**
 * Checks for Hangfire jobs using scoped services.
 *
 * @param {string} content - File content
 * @param {string} filePath - Path to the file
 * @returns {ConcurrencyIssue[]}
 */
export function checkHangfireWithScopedServices(content, filePath) {
  const issues = [];
  const lines = content.split('\n');

  // Check if file has Hangfire patterns
  const hasHangfire = HANGFIRE_PATTERNS.some((pattern) => pattern.test(content));

  if (!hasHangfire) {
    return issues;
  }

  // Check if using factory pattern
  const hasFactory = FACTORY_PATTERNS.some((pattern) => pattern.test(content));

  if (hasFactory) {
    return issues; // Good - using factory
  }

  // Check for scoped DbContext
  const hasScopedDbContext = SCOPED_DBCONTEXT_PATTERNS.some((pattern) => {
    const result = pattern.test(content);
    pattern.lastIndex = 0;
    return result;
  });

  if (hasScopedDbContext) {
    lines.forEach((line, index) => {
      HANGFIRE_PATTERNS.forEach((pattern) => {
        if (pattern.test(line)) {
          issues.push({
            type: 'error',
            message: 'Hangfire job com DbContext scoped',
            suggestion:
              'Hangfire jobs rodam fora do escopo HTTP. Use IDbContextFactory<T> para criar contextos isolados.',
            file: filePath,
            line: index + 1,
            code: line.trim(),
          });
        }
      });
    });
  }

  return issues;
}

/**
 * Checks for scoped services injected into singleton services.
 *
 * @param {string} content - File content
 * @param {string} filePath - Path to the file
 * @returns {ConcurrencyIssue[]}
 */
export function checkScopedInSingleton(content, filePath) {
  const issues = [];
  const lines = content.split('\n');

  // Check if class might be a singleton (common naming patterns)
  const isSingletonCandidate =
    filePath.includes('BackgroundService') ||
    filePath.includes('HostedService') ||
    filePath.includes('Worker') ||
    content.includes(': BackgroundService') ||
    content.includes(': IHostedService') ||
    content.includes('[Singleton]');

  if (!isSingletonCandidate) {
    return issues;
  }

  // Check for scoped service injection
  lines.forEach((line, index) => {
    SCOPED_DBCONTEXT_PATTERNS.forEach((pattern) => {
      if (pattern.test(line)) {
        issues.push({
          type: 'warning',
          message: 'Possivel injecao de servico scoped em singleton',
          suggestion:
            'Services scoped (DbContext, Repositories) nao devem ser injetados em singletons. Use IServiceScopeFactory ou IDbContextFactory.',
          file: filePath,
          line: index + 1,
          code: line.trim(),
        });
      }
      pattern.lastIndex = 0;
    });
  });

  return issues;
}

/**
 * Checks for concurrent DbContext access in Blazor components.
 *
 * @param {string} content - File content
 * @param {string} filePath - Path to the file
 * @returns {ConcurrencyIssue[]}
 */
export function checkConcurrentAccess(content, filePath) {
  const issues = [];
  const lines = content.split('\n');

  // Check for multiple Task.WhenAll with repository/context calls
  if (content.includes('Task.WhenAll') || content.includes('Task.WhenAny')) {
    // Check if the tasks involve database operations
    const hasDbOperations =
      content.includes('Repository') ||
      content.includes('DbContext') ||
      content.includes('SaveChangesAsync') ||
      content.includes('ToListAsync');

    if (hasDbOperations) {
      lines.forEach((line, index) => {
        if (line.includes('Task.WhenAll') || line.includes('Task.WhenAny')) {
          issues.push({
            type: 'warning',
            message: 'Task.WhenAll/WhenAny com possivel acesso concorrente ao DbContext',
            suggestion:
              'Se as tasks acessam o mesmo DbContext, isso causara erros. Cada task deve ter seu proprio contexto via IDbContextFactory.',
            file: filePath,
            line: index + 1,
            code: line.trim(),
          });
        }
      });
    }
  }

  return issues;
}

/**
 * Runs all concurrency checks on a file.
 *
 * @param {string} content - File content
 * @param {string} filePath - Path to the file
 * @returns {ConcurrencyIssue[]}
 */
export function analyzeConcurrency(content, filePath) {
  const issues = [];

  issues.push(...checkScopedDbContextInBackground(content, filePath));
  issues.push(...checkHangfireWithScopedServices(content, filePath));
  issues.push(...checkScopedInSingleton(content, filePath));
  issues.push(...checkConcurrentAccess(content, filePath));

  return issues;
}


export default {
  analyzeConcurrency,
  checkScopedDbContextInBackground,
  checkHangfireWithScopedServices,
  checkScopedInSingleton,
  checkConcurrentAccess,
  countIssues,
};
