/**
 * Repository Factory - Dependency Injection pattern for repositories
 *
 * @module platform-marketing-content/shared/repositories
 *
 * @example
 * ```typescript
 * import { RepositoryFactory, IBaseRepository } from '@shared/repositories';
 *
 * // Register repositories at app startup
 * const factory = RepositoryFactory.getInstance();
 * factory.register('ProjectRepository', new ProjectRepositoryImpl());
 *
 * // Get repository in services
 * const projectRepo = factory.getRequired<IBaseRepository<Project>>('ProjectRepository');
 * const project = await projectRepo.findById(ctx, 'project-id');
 * ```
 */

/**
 * Repository not found error
 */
export class RepositoryNotFoundError extends Error {
  constructor(repositoryName: string) {
    super(`Repository '${repositoryName}' not found in factory registry`);
    this.name = 'RepositoryNotFoundError';
  }
}

/**
 * Repository already registered error
 */
export class RepositoryAlreadyRegisteredError extends Error {
  constructor(repositoryName: string) {
    super(
      `Repository '${repositoryName}' is already registered. Use 'replace' to override.`,
    );
    this.name = 'RepositoryAlreadyRegisteredError';
  }
}

/**
 * Repository factory for managing repository instances
 *
 * Implements Singleton and Registry patterns for centralized
 * repository management and dependency injection.
 *
 * @example
 * ```typescript
 * // Initialize factory
 * const factory = RepositoryFactory.getInstance();
 *
 * // Register repositories
 * factory.register('ProjectRepository', projectRepository);
 * factory.register('ClientRepository', clientRepository);
 *
 * // Retrieve repositories
 * const projectRepo = factory.get<IBaseRepository<Project>>('ProjectRepository');
 * const clientRepo = factory.getRequired<IBaseRepository<Client>>('ClientRepository');
 *
 * // Check registration
 * if (factory.has('BrandRepository')) {
 *   const brandRepo = factory.get<IBaseRepository<Brand>>('BrandRepository');
 * }
 * ```
 */
export class RepositoryFactory {
  private static instance: RepositoryFactory;
  private repositories: Map<string, unknown>;

  /**
   * Private constructor for Singleton pattern
   */
  private constructor() {
    this.repositories = new Map<string, unknown>();
  }

  /**
   * Get singleton instance of RepositoryFactory
   *
   * @returns The singleton instance
   *
   * @example
   * ```typescript
   * const factory = RepositoryFactory.getInstance();
   * ```
   */
  public static getInstance(): RepositoryFactory {
    if (!RepositoryFactory.instance) {
      RepositoryFactory.instance = new RepositoryFactory();
    }
    return RepositoryFactory.instance;
  }

  /**
   * Register a repository instance
   *
   * @param name - Unique repository identifier
   * @param repository - Repository instance
   * @throws {RepositoryAlreadyRegisteredError} If repository name already exists
   *
   * @example
   * ```typescript
   * factory.register('ProjectRepository', new ProjectRepository(dataSource));
   * factory.register('ClientRepository', new ClientRepository(dataSource));
   * ```
   */
  public register<T>(name: string, repository: T): void {
    if (this.repositories.has(name)) {
      throw new RepositoryAlreadyRegisteredError(name);
    }
    this.repositories.set(name, repository);
  }

  /**
   * Register or replace an existing repository
   *
   * @param name - Unique repository identifier
   * @param repository - Repository instance
   *
   * @example
   * ```typescript
   * // Override existing repository for testing
   * factory.replace('ProjectRepository', mockProjectRepository);
   * ```
   */
  public replace<T>(name: string, repository: T): void {
    this.repositories.set(name, repository);
  }

  /**
   * Get a repository instance (returns undefined if not found)
   *
   * @param name - Repository identifier
   * @returns Repository instance or undefined
   *
   * @example
   * ```typescript
   * const projectRepo = factory.get<IBaseRepository<Project>>('ProjectRepository');
   * if (projectRepo) {
   *   const project = await projectRepo.findById(ctx, projectId);
   * }
   * ```
   */
  public get<T>(name: string): T | undefined {
    return this.repositories.get(name) as T | undefined;
  }

  /**
   * Get a required repository instance
   *
   * @param name - Repository identifier
   * @returns Repository instance
   * @throws {RepositoryNotFoundError} If repository not found
   *
   * @example
   * ```typescript
   * const projectRepo = factory.getRequired<IBaseRepository<Project>>('ProjectRepository');
   * const project = await projectRepo.findById(ctx, projectId);
   * ```
   */
  public getRequired<T>(name: string): T {
    const repository = this.repositories.get(name) as T | undefined;
    if (!repository) {
      throw new RepositoryNotFoundError(name);
    }
    return repository;
  }

  /**
   * Check if a repository is registered
   *
   * @param name - Repository identifier
   * @returns True if repository exists
   *
   * @example
   * ```typescript
   * if (factory.has('BrandRepository')) {
   *   const brandRepo = factory.get<IBaseRepository<Brand>>('BrandRepository');
   * }
   * ```
   */
  public has(name: string): boolean {
    return this.repositories.has(name);
  }

  /**
   * Unregister a repository
   *
   * @param name - Repository identifier
   * @returns True if repository was removed
   *
   * @example
   * ```typescript
   * factory.unregister('TempRepository');
   * ```
   */
  public unregister(name: string): boolean {
    return this.repositories.delete(name);
  }

  /**
   * Clear all registered repositories
   *
   * Useful for testing scenarios
   *
   * @example
   * ```typescript
   * afterEach(() => {
   *   factory.clear();
   * });
   * ```
   */
  public clear(): void {
    this.repositories.clear();
  }

  /**
   * Get all registered repository names
   *
   * @returns Array of repository names
   *
   * @example
   * ```typescript
   * const names = factory.getRegisteredNames();
   * console.log('Registered repositories:', names);
   * ```
   */
  public getRegisteredNames(): string[] {
    return Array.from(this.repositories.keys());
  }

  /**
   * Get count of registered repositories
   *
   * @returns Number of registered repositories
   *
   * @example
   * ```typescript
   * console.log(`Total repositories: ${factory.count()}`);
   * ```
   */
  public count(): number {
    return this.repositories.size;
  }

  /**
   * Register multiple repositories at once
   *
   * @param repositories - Map of repository name to instance
   *
   * @example
   * ```typescript
   * factory.registerBatch({
   *   ProjectRepository: new ProjectRepository(dataSource),
   *   ClientRepository: new ClientRepository(dataSource),
   *   BrandRepository: new BrandRepository(dataSource),
   * });
   * ```
   */
  public registerBatch(repositories: Record<string, unknown>): void {
    Object.entries(repositories).forEach(([name, repository]) => {
      this.register(name, repository);
    });
  }

  /**
   * Clone factory instance with same repositories
   *
   * Useful for creating isolated scopes in testing
   *
   * @returns New factory instance with cloned registry
   *
   * @example
   * ```typescript
   * const testFactory = factory.clone();
   * testFactory.replace('ProjectRepository', mockProjectRepository);
   * ```
   */
  public clone(): RepositoryFactory {
    const cloned = new RepositoryFactory();
    this.repositories.forEach((repository, name) => {
      cloned.register(name, repository);
    });
    return cloned;
  }
}

/**
 * Helper function to create and configure a repository factory
 *
 * @param repositories - Optional initial repositories
 * @returns Configured RepositoryFactory instance
 *
 * @example
 * ```typescript
 * const factory = createRepositoryFactory({
 *   ProjectRepository: new ProjectRepository(dataSource),
 *   ClientRepository: new ClientRepository(dataSource),
 * });
 * ```
 */
export function createRepositoryFactory(
  repositories?: Record<string, unknown>,
): RepositoryFactory {
  const factory = RepositoryFactory.getInstance();

  if (repositories) {
    factory.registerBatch(repositories);
  }

  return factory;
}

/**
 * Decorator for automatic repository injection
 *
 * @param repositoryName - Name of repository to inject
 * @returns Property decorator
 *
 * @example
 * ```typescript
 * class ProjectService {
 *   @InjectRepository('ProjectRepository')
 *   private projectRepository: IBaseRepository<Project>;
 *
 *   async getProject(ctx: ServiceContext, id: string) {
 *     return this.projectRepository.findById(ctx, id);
 *   }
 * }
 * ```
 */
export function InjectRepository(repositoryName: string) {
  return function (target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
      get() {
        return RepositoryFactory.getInstance().getRequired(repositoryName);
      },
      enumerable: true,
      configurable: true,
    });
  };
}
