import {
  IConnectionQueryRepository,
  ConnectionQueryOptions,
  PaginatedConnectionQueryResult,
  ConnectionForUrlDTO,
  ConnectionForUserDTO,
  ConnectionSortField,
  SortOrder,
} from '../../domain/IConnectionQueryRepository';
import { InMemoryConnectionRepository } from './InMemoryConnectionRepository';
import { UrlOrCardIdType } from '../../domain/value-objects/UrlOrCardId';
import { ConnectionTypeEnum } from '../../domain/value-objects/ConnectionType';

export class InMemoryConnectionQueryRepository
  implements IConnectionQueryRepository
{
  constructor(private connectionRepository: InMemoryConnectionRepository) {}

  async getForwardConnectionsForUrl(
    url: string,
    options: ConnectionQueryOptions,
  ): Promise<PaginatedConnectionQueryResult<ConnectionForUrlDTO>> {
    return this.getConnectionsForUrlPrivate(url, 'source', options);
  }

  async getBackwardConnectionsForUrl(
    url: string,
    options: ConnectionQueryOptions,
  ): Promise<PaginatedConnectionQueryResult<ConnectionForUrlDTO>> {
    return this.getConnectionsForUrlPrivate(url, 'target', options);
  }

  async getConnectionsForUrl(
    url: string,
    direction: 'forward' | 'backward' | 'both',
    options: ConnectionQueryOptions,
  ): Promise<PaginatedConnectionQueryResult<ConnectionForUserDTO>> {
    const { page, limit, sortBy, sortOrder, connectionTypes } = options;

    // Get all connections from the repository
    const allConnections = this.connectionRepository.getAllConnections();

    // Filter connections based on direction and URL type
    let filteredConnections = allConnections.filter((connection) => {
      const isSourceMatch =
        connection.source.type === UrlOrCardIdType.URL &&
        connection.source.stringValue === url;
      const isTargetMatch =
        connection.target.type === UrlOrCardIdType.URL &&
        connection.target.stringValue === url;
      const bothUrlToUrl =
        connection.source.type === UrlOrCardIdType.URL &&
        connection.target.type === UrlOrCardIdType.URL;

      if (direction === 'forward') {
        return isSourceMatch && bothUrlToUrl;
      } else if (direction === 'backward') {
        return isTargetMatch && bothUrlToUrl;
      } else {
        // both
        return (isSourceMatch || isTargetMatch) && bothUrlToUrl;
      }
    });

    // Apply connection type filter if provided
    if (connectionTypes && connectionTypes.length > 0) {
      filteredConnections = filteredConnections.filter((connection) =>
        connection.type
          ? connectionTypes.includes(connection.type.value)
          : false,
      );
    }

    // Sort connections
    filteredConnections.sort((a, b) => {
      const dateA =
        sortBy === ConnectionSortField.UPDATED_AT ? a.updatedAt : a.createdAt;
      const dateB =
        sortBy === ConnectionSortField.UPDATED_AT ? b.updatedAt : b.createdAt;

      const comparison = dateA.getTime() - dateB.getTime();
      return sortOrder === SortOrder.ASC ? comparison : -comparison;
    });

    // Apply pagination
    const offset = (page - 1) * limit;
    const paginatedConnections = filteredConnections.slice(
      offset,
      offset + limit,
    );

    // Map to DTOs with both source and target
    const items: ConnectionForUserDTO[] = paginatedConnections.map(
      (connection) => ({
        connection: {
          id: connection.connectionId.getStringValue(),
          type: connection.type?.value,
          note: connection.note?.value,
          createdAt: connection.createdAt,
          updatedAt: connection.updatedAt,
          curatorId: connection.curatorId.value,
        },
        sourceUrl: connection.source.stringValue,
        targetUrl: connection.target.stringValue,
      }),
    );

    return {
      items,
      totalCount: filteredConnections.length,
      hasMore:
        offset + paginatedConnections.length < filteredConnections.length,
    };
  }

  private async getConnectionsForUrlPrivate(
    url: string,
    direction: 'source' | 'target',
    options: ConnectionQueryOptions,
  ): Promise<PaginatedConnectionQueryResult<ConnectionForUrlDTO>> {
    const { page, limit, sortBy, sortOrder, connectionTypes } = options;

    // Get all connections from the repository
    const allConnections = this.connectionRepository.getAllConnections();

    // Filter connections based on direction and URL type
    let filteredConnections = allConnections.filter((connection) => {
      if (direction === 'source') {
        // Forward: source must be URL matching the input, target must also be URL
        return (
          connection.source.type === UrlOrCardIdType.URL &&
          connection.source.stringValue === url &&
          connection.target.type === UrlOrCardIdType.URL
        );
      } else {
        // Backward: target must be URL matching the input, source must also be URL
        return (
          connection.target.type === UrlOrCardIdType.URL &&
          connection.target.stringValue === url &&
          connection.source.type === UrlOrCardIdType.URL
        );
      }
    });

    // Apply connection type filter if provided
    if (connectionTypes && connectionTypes.length > 0) {
      filteredConnections = filteredConnections.filter((connection) =>
        connection.type
          ? connectionTypes.includes(connection.type.value)
          : false,
      );
    }

    // Sort connections
    filteredConnections.sort((a, b) => {
      const dateA =
        sortBy === ConnectionSortField.UPDATED_AT ? a.updatedAt : a.createdAt;
      const dateB =
        sortBy === ConnectionSortField.UPDATED_AT ? b.updatedAt : b.createdAt;

      const comparison = dateA.getTime() - dateB.getTime();
      return sortOrder === SortOrder.ASC ? comparison : -comparison;
    });

    // Apply pagination
    const offset = (page - 1) * limit;
    const paginatedConnections = filteredConnections.slice(
      offset,
      offset + limit,
    );

    // Map to DTOs
    const items: ConnectionForUrlDTO[] = paginatedConnections.map(
      (connection) => ({
        connection: {
          id: connection.connectionId.getStringValue(),
          type: connection.type?.value,
          note: connection.note?.value,
          createdAt: connection.createdAt,
          updatedAt: connection.updatedAt,
          curatorId: connection.curatorId.value,
        },
        // Return the URL from the opposite direction
        url:
          direction === 'source'
            ? connection.target.stringValue
            : connection.source.stringValue,
      }),
    );

    return {
      items,
      totalCount: filteredConnections.length,
      hasMore:
        offset + paginatedConnections.length < filteredConnections.length,
    };
  }

  async getConnectionsForUser(
    curatorId: string,
    options: ConnectionQueryOptions,
  ): Promise<PaginatedConnectionQueryResult<ConnectionForUserDTO>> {
    const { page, limit, sortBy, sortOrder, connectionTypes } = options;

    // Get all connections from the repository
    const allConnections = this.connectionRepository.getAllConnections();

    // Filter connections by curator and URL-to-URL type
    let filteredConnections = allConnections.filter((connection) => {
      return (
        connection.curatorId.value === curatorId &&
        connection.source.type === UrlOrCardIdType.URL &&
        connection.target.type === UrlOrCardIdType.URL
      );
    });

    // Apply connection type filter if provided
    if (connectionTypes && connectionTypes.length > 0) {
      filteredConnections = filteredConnections.filter((connection) =>
        connection.type
          ? connectionTypes.includes(connection.type.value)
          : false,
      );
    }

    // Sort connections
    filteredConnections.sort((a, b) => {
      const dateA =
        sortBy === ConnectionSortField.UPDATED_AT ? a.updatedAt : a.createdAt;
      const dateB =
        sortBy === ConnectionSortField.UPDATED_AT ? b.updatedAt : b.createdAt;

      const comparison = dateA.getTime() - dateB.getTime();
      return sortOrder === SortOrder.ASC ? comparison : -comparison;
    });

    // Apply pagination
    const offset = (page - 1) * limit;
    const paginatedConnections = filteredConnections.slice(
      offset,
      offset + limit,
    );

    // Map to DTOs
    const items: ConnectionForUserDTO[] = paginatedConnections.map(
      (connection) => ({
        connection: {
          id: connection.connectionId.getStringValue(),
          type: connection.type?.value,
          note: connection.note?.value,
          createdAt: connection.createdAt,
          updatedAt: connection.updatedAt,
          curatorId: connection.curatorId.value,
        },
        sourceUrl: connection.source.stringValue,
        targetUrl: connection.target.stringValue,
      }),
    );

    return {
      items,
      totalCount: filteredConnections.length,
      hasMore:
        offset + paginatedConnections.length < filteredConnections.length,
    };
  }

  async getConnectionStatsForUrl(url: string): Promise<{
    forwardTotal: number;
    backwardTotal: number;
    forwardByType: Map<ConnectionTypeEnum, number>;
    backwardByType: Map<ConnectionTypeEnum, number>;
  }> {
    const allConnections = this.connectionRepository.getAllConnections();

    // Forward connections (where URL is source)
    const forwardConnections = allConnections.filter(
      (connection) =>
        connection.source.type === UrlOrCardIdType.URL &&
        connection.source.stringValue === url &&
        connection.target.type === UrlOrCardIdType.URL,
    );

    // Backward connections (where URL is target)
    const backwardConnections = allConnections.filter(
      (connection) =>
        connection.target.type === UrlOrCardIdType.URL &&
        connection.target.stringValue === url &&
        connection.source.type === UrlOrCardIdType.URL,
    );

    // Count by type for forward connections
    const forwardByType = new Map<ConnectionTypeEnum, number>();
    forwardConnections.forEach((connection) => {
      if (connection.type) {
        const current = forwardByType.get(connection.type.value) || 0;
        forwardByType.set(connection.type.value, current + 1);
      }
    });

    // Count by type for backward connections
    const backwardByType = new Map<ConnectionTypeEnum, number>();
    backwardConnections.forEach((connection) => {
      if (connection.type) {
        const current = backwardByType.get(connection.type.value) || 0;
        backwardByType.set(connection.type.value, current + 1);
      }
    });

    return {
      forwardTotal: forwardConnections.length,
      backwardTotal: backwardConnections.length,
      forwardByType,
      backwardByType,
    };
  }

  async getConnectionStatsForCurator(curatorId: string): Promise<{
    total: number;
    byType: Map<ConnectionTypeEnum, number>;
  }> {
    const allConnections = this.connectionRepository.getAllConnections();

    // Get all URL-to-URL connections created by this curator
    const curatorConnections = allConnections.filter(
      (connection) =>
        connection.curatorId.value === curatorId &&
        connection.source.type === UrlOrCardIdType.URL &&
        connection.target.type === UrlOrCardIdType.URL,
    );

    // Count by type
    const byType = new Map<ConnectionTypeEnum, number>();
    curatorConnections.forEach((connection) => {
      if (connection.type) {
        const current = byType.get(connection.type.value) || 0;
        byType.set(connection.type.value, current + 1);
      }
    });

    return {
      total: curatorConnections.length,
      byType,
    };
  }
}
