import { EnvironmentConfigService } from '../../config/EnvironmentConfigService';
import { jwtConfig, oauthConfig } from '../../config';
import { JwtTokenService } from '../../../../modules/user/infrastructure/services/JwtTokenService';
import {
  AtProtoOAuthProcessor,
  OAuthClientFactory,
} from '../../../../modules/user/infrastructure';
import { UserAuthenticationService } from '../../../../modules/user/infrastructure/services/UserAuthenticationService';
import { ATProtoAgentService } from '../../../../modules/atproto/infrastructure/services/ATProtoAgentService';
import { IFramelyMetadataService } from '../../../../modules/cards/infrastructure/IFramelyMetadataService';
import { CitoidMetadataService } from '../../../../modules/cards/infrastructure/CitoidMetadataService';
import { CompositeMetadataService } from '../../../../modules/cards/infrastructure/CompositeMetadataService';
import { CachedMetadataService } from '../../../../modules/cards/infrastructure/CachedMetadataService';
import { BlueskyProfileService } from '../../../../modules/atproto/infrastructure/services/BlueskyProfileService';
import { CachedBlueskyProfileService } from '../../../../modules/atproto/infrastructure/services/CachedBlueskyProfileService';
import { ATProtoCollectionPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoCollectionPublisher';
import { ATProtoCardPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoCardPublisher';
import { ATProtoConnectionPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoConnectionPublisher';
import { ATProtoFollowPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoFollowPublisher';
import { FakeCollectionPublisher } from '../../../../modules/cards/tests/utils/FakeCollectionPublisher';
import { FakeCardPublisher } from '../../../../modules/cards/tests/utils/FakeCardPublisher';
import { FakeConnectionPublisher } from '../../../../modules/cards/tests/utils/FakeConnectionPublisher';
import { FakeFollowPublisher } from '../../../../modules/atproto/infrastructure/publishers/FakeFollowPublisher';
import { CardLibraryService } from '../../../../modules/cards/domain/services/CardLibraryService';
import { CardCollectionService } from '../../../../modules/cards/domain/services/CardCollectionService';
import { AuthMiddleware } from '../middleware/AuthMiddleware';
import { StatsApiKeyMiddleware } from '../../../../modules/user/infrastructure/http/middleware/StatsApiKeyMiddleware';
import { Repositories } from './RepositoryFactory';
import { NodeOAuthClient } from '@atproto/oauth-client-node';
import { AppPasswordSessionService } from 'src/modules/atproto/infrastructure/services/AppPasswordSessionService';
import { AtpAppPasswordProcessor } from 'src/modules/atproto/infrastructure/services/AtpAppPasswordProcessor';
import { ICollectionPublisher } from 'src/modules/cards/application/ports/ICollectionPublisher';
import { ICardPublisher } from 'src/modules/cards/application/ports/ICardPublisher';
import { IConnectionPublisher } from 'src/modules/cards/application/ports/IConnectionPublisher';
import { IFollowPublisher } from 'src/modules/user/application/ports/IFollowPublisher';
import { IMetadataService } from 'src/modules/cards/domain/services/IMetadataService';
import { BullMQEventSubscriber } from '../../events/BullMQEventSubscriber';
import { BullMQEventPublisher } from '../../events/BullMQEventPublisher';
import { InMemoryEventPublisher } from '../../events/InMemoryEventPublisher';
import { InMemoryEventSubscriber } from '../../events/InMemoryEventSubscriber';
import Redis from 'ioredis';
// Mock/Fake service imports
import { FakeJwtTokenService } from '../../../../modules/user/infrastructure/services/FakeJwtTokenService';
import { FakeAtProtoOAuthProcessor } from '../../../../modules/atproto/infrastructure/services/FakeAtProtoOAuthProcessor';
import { FakeUserAuthenticationService } from '../../../../modules/user/infrastructure/services/FakeUserAuthenticationService';
import { FakeAgentService } from '../../../../modules/atproto/infrastructure/services/FakeAgentService';
import { FakeAppPasswordSessionService } from '../../../../modules/atproto/infrastructure/services/FakeAppPasswordSessionService';
import { FakeAtpAppPasswordProcessor } from '../../../../modules/atproto/infrastructure/services/FakeAtpAppPasswordProcessor';
import { ITokenService } from 'src/modules/user/application/services/ITokenService';
import { IOAuthProcessor } from 'src/modules/user/application/services/IOAuthProcessor';
import { IAppPasswordProcessor } from 'src/modules/atproto/application/IAppPasswordProcessor';
import { IUserAuthenticationService } from 'src/modules/user/domain/services/IUserAuthenticationService';
import { IAgentService } from 'src/modules/atproto/application/IAgentService';
import { IProfileService } from 'src/modules/cards/domain/services/IProfileService';
import { IEventPublisher } from '../../../application/events/IEventPublisher';
import { QueueName } from '../../events/QueueConfig';
import { RedisFactory } from '../../redis/RedisFactory';
import { IEventSubscriber } from 'src/shared/application/events/IEventSubscriber';
import { FeedService } from '../../../../modules/feeds/domain/services/FeedService';
import { ATProtoIdentityResolutionService } from '../../../../modules/atproto/infrastructure/services/ATProtoIdentityResolutionService';
import { CachedATProtoIdentityResolutionService } from '../../../../modules/atproto/infrastructure/services/CachedATProtoIdentityResolutionService';
import { IIdentityResolutionService } from '../../../../modules/atproto/domain/services/IIdentityResolutionService';
import { CookieService } from '../services/CookieService';
import { InMemorySagaStateStore } from '../../../../modules/feeds/infrastructure/InMemorySagaStateStore';
import { RedisSagaStateStore } from '../../../../modules/feeds/infrastructure/RedisSagaStateStore';
import { ISagaStateStore } from 'src/modules/feeds/application/sagas/ISagaStateStore';
import { SearchService } from '../../../../modules/search/domain/services/SearchService';
import { IVectorDatabase } from '../../../../modules/search/domain/IVectorDatabase';
import { InMemoryVectorDatabase } from '../../../../modules/search/infrastructure/InMemoryVectorDatabase';
import { UpstashVectorDatabase } from '../../../../modules/search/infrastructure/UpstashVectorDatabase';
import { NotificationService } from '../../../../modules/notifications/domain/services/NotificationService';
import { FakeLeafletSearchService } from 'src/modules/search/infrastructure/FakeLeafletSearchService';
import { ILeafletSearchService } from 'src/modules/search/domain/services/ILeafletSearchService';
import { ConstellationLeafletSearchService } from 'src/modules/search/domain/services/ConstellationLeafletSearchService';
import { CachedLeafletSearchService } from 'src/modules/search/infrastructure/CachedLeafletSearchService';
import { IAtProtoRepoService } from '../../../../modules/atproto/application/IAtProtoRepoService';
import { ATProtoRepoService } from '../../../../modules/atproto/infrastructure/services/ATProtoRepoService';
import { FakeAtProtoRepoService } from '../../../../modules/atproto/infrastructure/services/FakeAtProtoRepoService';
import { DistributedLockServiceFactory } from '../../locking/DistributedLockServiceFactory';

// Shared services needed by both web app and workers
export interface SharedServices {
  tokenService: ITokenService;
  userAuthService: IUserAuthenticationService;
  atProtoAgentService: IAgentService;
  atProtoRepoService: IAtProtoRepoService;
  metadataService: IMetadataService;
  profileService: IProfileService;
  feedService: FeedService;
  notificationService: NotificationService;
  nodeOauthClient: NodeOAuthClient;
  identityResolutionService: IIdentityResolutionService;
  configService: EnvironmentConfigService;
  cookieService: CookieService;
  searchService: SearchService;
  leafletSearchService: ILeafletSearchService;
  cardLibraryService: CardLibraryService;
  cardCollectionService: CardCollectionService;
  eventPublisher: IEventPublisher;
  collectionPublisher: ICollectionPublisher;
  connectionPublisher: IConnectionPublisher;
  followPublisher: IFollowPublisher;
}

// Web app specific services (includes publishers, auth middleware)
export interface WebAppServices extends SharedServices {
  oauthProcessor: IOAuthProcessor;
  appPasswordProcessor: IAppPasswordProcessor;
  cardPublisher: ICardPublisher;
  authMiddleware: AuthMiddleware;
  statsApiKeyMiddleware: StatsApiKeyMiddleware;
}

// Worker specific services (includes subscribers)
export interface WorkerServices extends SharedServices {
  redisConnection: Redis | null;
  createEventSubscriber: (queueName: QueueName) => IEventSubscriber;
  sagaStateStore: ISagaStateStore;
}

// Legacy interface for backward compatibility
export interface Services extends WebAppServices {}

export class ServiceFactory {
  static create(
    configService: EnvironmentConfigService,
    repositories: Repositories,
  ): Services {
    return this.createForWebApp(configService, repositories);
  }

  static createForWebApp(
    configService: EnvironmentConfigService,
    repositories: Repositories,
  ): WebAppServices {
    const sharedServices = this.createSharedServices(
      configService,
      repositories,
    );

    const useMockAuth = configService.shouldUseMockAuth();

    // App Password Session Service
    const appPasswordSessionService = useMockAuth
      ? new FakeAppPasswordSessionService()
      : new AppPasswordSessionService(
          repositories.appPasswordSessionRepository,
        );

    // App Password Processor
    const appPasswordProcessor = useMockAuth
      ? new FakeAtpAppPasswordProcessor()
      : new AtpAppPasswordProcessor(appPasswordSessionService);

    // OAuth Processor
    const oauthProcessor = useMockAuth
      ? new FakeAtProtoOAuthProcessor(sharedServices.tokenService)
      : new AtProtoOAuthProcessor(sharedServices.nodeOauthClient);

    const useFakePublishers = configService.shouldUseFakePublishers();
    const collections = configService.getAtProtoCollections();

    const cardPublisher = useFakePublishers
      ? new FakeCardPublisher()
      : new ATProtoCardPublisher(
          sharedServices.atProtoAgentService,
          collections.card,
        );

    const authMiddleware = new AuthMiddleware(
      sharedServices.tokenService,
      sharedServices.cookieService,
    );

    const statsApiKeyMiddleware = new StatsApiKeyMiddleware(
      configService.getStatsConfig().apiKey,
    );

    return {
      ...sharedServices,
      oauthProcessor,
      appPasswordProcessor,
      cardPublisher,
      authMiddleware,
      statsApiKeyMiddleware,
    };
  }

  static createForWorker(
    configService: EnvironmentConfigService,
    repositories: Repositories,
  ): WorkerServices {
    const sharedServices = this.createSharedServices(
      configService,
      repositories,
    );

    const useInMemoryEvents = configService.shouldUseInMemoryEvents();

    let eventPublisher: IEventPublisher;
    let redisConnection: Redis | null = null;
    let createEventSubscriber: (queueName: QueueName) => IEventSubscriber;

    if (useInMemoryEvents) {
      eventPublisher = new InMemoryEventPublisher();
      createEventSubscriber = (queueName: QueueName) => {
        return new InMemoryEventSubscriber();
      };
    } else {
      // Redis connection is required for BullMQ workers
      if (!process.env.REDIS_URL) {
        throw new Error(
          'REDIS_URL environment variable is required for BullMQ worker services',
        );
      }

      const redisConfig = configService.getWorkersConfig().redisConfig;
      redisConnection = RedisFactory.createConnection(redisConfig);
      eventPublisher = new BullMQEventPublisher(redisConnection);

      createEventSubscriber = (queueName: QueueName) => {
        return new BullMQEventSubscriber(redisConnection!, { queueName });
      };
    }

    // Create appropriate saga state store
    const sagaStateStore = useInMemoryEvents
      ? new InMemorySagaStateStore()
      : new RedisSagaStateStore(redisConnection!);

    return {
      ...sharedServices,
      redisConnection: redisConnection,
      createEventSubscriber,
      sagaStateStore,
    };
  }

  private static createSharedServices(
    configService: EnvironmentConfigService,
    repositories: Repositories,
  ): SharedServices {
    const useMockAuth = configService.shouldUseMockAuth();

    const nodeOauthClient = OAuthClientFactory.createClient(
      repositories.oauthStateStore,
      repositories.oauthSessionStore,
      oauthConfig.baseUrl,
    );

    // Token Service
    const tokenService = useMockAuth
      ? new FakeJwtTokenService(repositories.tokenRepository)
      : new JwtTokenService(
          repositories.tokenRepository,
          jwtConfig.jwtSecret,
          jwtConfig.accessTokenExpiresIn,
          jwtConfig.refreshTokenExpiresIn,
        );

    // User Authentication Service
    const userAuthService = useMockAuth
      ? new FakeUserAuthenticationService(repositories.userRepository)
      : new UserAuthenticationService(repositories.userRepository);

    // App Password Session Service (needed for ATProto Agent Service)
    const appPasswordSessionService = useMockAuth
      ? new FakeAppPasswordSessionService()
      : new AppPasswordSessionService(
          repositories.appPasswordSessionRepository,
        );

    // ATProto Agent Service
    const atProtoAgentService = useMockAuth
      ? new FakeAgentService()
      : new ATProtoAgentService(
          nodeOauthClient,
          appPasswordSessionService,
          configService,
        );

    // ATProto Repo Service
    const atProtoRepoService = useMockAuth
      ? new FakeAtProtoRepoService()
      : new ATProtoRepoService(atProtoAgentService);

    // Create individual metadata services
    const baseIframelyService = new IFramelyMetadataService(
      configService.getIFramelyApiKey(),
    );
    const baseCitoidService = new CitoidMetadataService();

    // Apply caching conditionally (similar to profile service)
    const useMockPersistence = configService.shouldUseMockPersistence();

    let iframelyService: IMetadataService;
    let citoidService: IMetadataService;

    if (useMockPersistence) {
      // No caching for mock persistence
      iframelyService = baseIframelyService;
      citoidService = baseCitoidService;
    } else {
      // Create Redis connection for caching
      const redisConfig = configService.getRedisConfig();
      const redis = RedisFactory.createConnection(redisConfig);

      // Wrap services with caching - different TTLs for different services
      iframelyService = new CachedMetadataService(
        baseIframelyService,
        redis,
        'iframely',
        3600 * 24 * 7, // 7 day TTL
      );
      citoidService = new CachedMetadataService(
        baseCitoidService,
        redis,
        'citoid',
        3600 * 24 * 7, // 7 day TTL
      );
    }

    // Create composite metadata service
    const metadataService = new CompositeMetadataService(
      iframelyService,
      citoidService,
    );

    // Profile Service with Redis caching
    const baseProfileService = new BlueskyProfileService(atProtoAgentService);

    let profileService: IProfileService;

    // caching requires persistence
    if (useMockPersistence) {
      profileService = baseProfileService;
    } else {
      // Create Redis connection for caching
      const redisConfig = configService.getRedisConfig();
      const redis = RedisFactory.createConnection(redisConfig);
      profileService = new CachedBlueskyProfileService(
        baseProfileService,
        redis,
        repositories.followsRepository,
      );
    }

    // Feed Service with distributed locking
    const distributedLockService = DistributedLockServiceFactory.create();
    const feedService = new FeedService(
      repositories.feedRepository,
      distributedLockService,
    );

    // Notification Service
    const notificationService = new NotificationService(
      repositories.notificationRepository,
    );

    // Identity Resolution Service with Redis caching
    const baseIdentityResolutionService = new ATProtoIdentityResolutionService(
      atProtoAgentService,
    );

    let identityResolutionService: IIdentityResolutionService;

    // caching requires persistence
    if (useMockPersistence) {
      identityResolutionService = baseIdentityResolutionService;
    } else {
      // Create Redis connection for caching
      const redisConfig = configService.getRedisConfig();
      const redis = RedisFactory.createConnection(redisConfig);
      identityResolutionService = new CachedATProtoIdentityResolutionService(
        baseIdentityResolutionService,
        redis,
      );
    }

    // Cookie Service
    const cookieService = new CookieService(configService);

    // Create vector database and search service (shared by both web app and workers)
    const useMockVectorDb = configService.shouldUseMockVectorDb();

    const vectorDatabase: IVectorDatabase = useMockVectorDb
      ? InMemoryVectorDatabase.getInstance()
      : new UpstashVectorDatabase(
          configService.getUpstashConfig().vectorUrl,
          configService.getUpstashConfig().vectorToken,
        );

    const searchService = new SearchService(
      vectorDatabase,
      metadataService,
      repositories.cardQueryRepository,
    );

    // Create LeafletSearchService with caching
    let leafletSearchService: ILeafletSearchService;

    if (useMockPersistence) {
      // Use fake implementation for mock persistence
      leafletSearchService = new FakeLeafletSearchService();
    } else {
      // Use real Constellation service with caching
      const baseLeafletSearchService = new ConstellationLeafletSearchService(
        metadataService,
      );
      const redisConfig = configService.getRedisConfig();
      const redis = RedisFactory.createConnection(redisConfig);
      leafletSearchService = new CachedLeafletSearchService(
        baseLeafletSearchService,
        redis,
      );
    }

    // Create publishers needed for shared services
    const useFakePublishers = configService.shouldUseFakePublishers();
    const collections = configService.getAtProtoCollections();

    const collectionPublisher = useFakePublishers
      ? new FakeCollectionPublisher()
      : new ATProtoCollectionPublisher(
          atProtoAgentService,
          collections.collection,
          collections.collectionLink,
          collections.collectionLinkRemoval,
        );

    const connectionPublisher = useFakePublishers
      ? new FakeConnectionPublisher()
      : new ATProtoConnectionPublisher(
          atProtoAgentService,
          collections.connection,
        );

    const followPublisher = useFakePublishers
      ? new FakeFollowPublisher()
      : new ATProtoFollowPublisher(
          atProtoAgentService,
          collections.follow,
          repositories.collectionRepository,
        );

    const cardPublisher = useFakePublishers
      ? new FakeCardPublisher()
      : new ATProtoCardPublisher(atProtoAgentService, collections.card);

    // Create domain services
    const cardCollectionService = new CardCollectionService(
      repositories.collectionRepository,
      collectionPublisher,
      repositories.cardRepository,
    );
    const cardLibraryService = new CardLibraryService(
      repositories.cardRepository,
      cardPublisher,
      repositories.collectionRepository,
      cardCollectionService,
    );

    // Create event publisher
    const useInMemoryEvents = configService.shouldUseInMemoryEvents();

    let eventPublisher: IEventPublisher;
    if (useInMemoryEvents) {
      eventPublisher = new InMemoryEventPublisher();
    } else {
      const redisConnection = RedisFactory.createConnection(
        configService.getWorkersConfig().redisConfig,
      );
      eventPublisher = new BullMQEventPublisher(redisConnection);
    }

    return {
      tokenService,
      userAuthService,
      atProtoAgentService,
      atProtoRepoService,
      metadataService,
      profileService,
      feedService,
      notificationService,
      nodeOauthClient,
      identityResolutionService,
      configService,
      cookieService,
      searchService,
      leafletSearchService,
      cardLibraryService,
      cardCollectionService,
      eventPublisher,
      collectionPublisher,
      connectionPublisher,
      followPublisher,
    };
  }
}
