/**
 * @fileoverview QualityMetrics repository implementation
 *
 * Concrete implementation of IQualityMetricsRepository using DeepSource API.
 */

import { IQualityMetricsRepository } from '../../domain/aggregates/quality-metrics/quality-metrics.repository.js';
import { QualityMetrics } from '../../domain/aggregates/quality-metrics/quality-metrics.aggregate.js';
import { ProjectKey } from '../../types/branded.js';
import { MetricShortcode, MetricKey } from '../../models/metrics.js';
import { QualityMetricsId } from '../../domain/aggregates/quality-metrics/quality-metrics.types.js';
import { DeepSourceClient } from '../../deepsource.js';
import { QualityMetricsMapper } from '../mappers/quality-metrics.mapper.js';
import { createLogger } from '../../utils/logging/logger.js';
import { BaseDeepSourceClient } from '../../client/base-client.js';
import type { AxiosInstance } from 'axios';

const logger = createLogger('QualityMetricsRepository');

/**
 * Concrete implementation of IQualityMetricsRepository using DeepSource API
 *
 * This repository provides access to QualityMetrics aggregates by fetching data
 * from the DeepSource API and mapping it to domain models.
 *
 * Note: The DeepSource API returns metrics at the repository level with items
 * for each context (language). This repository maps each item to a separate
 * QualityMetrics aggregate. This ensures fresh data retrieval on every request
 * as per requirements.
 */
export class QualityMetricsRepository implements IQualityMetricsRepository {
  constructor(private readonly client: DeepSourceClient) {
    // client is stored for use in methods
  }

  /**
   * Helper method to get repository ID for a project
   *
   * @param projectKey - The project key
   * @returns The repository GraphQL ID
   */
  private async getRepositoryId(projectKey: ProjectKey): Promise<string> {
    const projects = await this.client.listProjects();
    const project = projects.find((p) => p.key === projectKey);
    if (!project) {
      throw new Error(`Project not found: ${projectKey}`);
    }

    // The listProjects doesn't include repository ID, so we need to fetch it
    // using a repository query. We can get it from any repository-specific query.
    // Let's use the runs query with minimal parameters to get the repository ID.
    try {
      const runs = await this.client.listRuns(projectKey, { first: 1 });
      // The runs query includes repository information with ID
      if (runs.items.length > 0 && runs.items[0]?.repository?.id) {
        return runs.items[0].repository.id;
      }

      // If no runs exist, we need to make a direct repository query
      // This is a fallback that uses the GraphQL client directly
      const repoQuery = `
        query($login: String!, $name: String!, $provider: VCSProvider!) {
          repository(login: $login, name: $name, vcsProvider: $provider) {
            id
          }
        }
      `;

      // Access protected client member via type assertion
      const clientWithAxios = this.client as unknown as BaseDeepSourceClient & {
        client: AxiosInstance;
      };
      const response = await clientWithAxios.client.post('', {
        query: repoQuery.trim(),
        variables: {
          login: project.repository.login,
          name: project.name,
          provider: project.repository.provider,
        },
      });

      if (response.data.errors) {
        throw new Error(
          `GraphQL Errors: ${response.data.errors.map((e: { message: string }) => e.message).join(', ')}`
        );
      }

      const repositoryId = response.data.data?.repository?.id;
      if (!repositoryId) {
        throw new Error(`Repository ID not found for project: ${projectKey}`);
      }

      return repositoryId;
    } catch (error) {
      logger.error('Error fetching repository ID', { projectKey, error });
      throw error;
    }
  }

  /**
   * Finds metrics by composite ID
   *
   * @param id - The composite ID (projectKey:metricKey:shortcode)
   * @returns The metrics if found, null otherwise
   */
  async findById(id: string): Promise<QualityMetrics | null> {
    try {
      logger.debug('Finding metrics by composite ID', { id });

      // Parse composite ID
      const parts = id.split(':');
      if (parts.length !== 3) {
        logger.error('Invalid composite ID format', { id });
        return null;
      }

      const [projectKey, metricKey, shortcode] = parts;
      return this.findByProjectAndMetric(
        projectKey as ProjectKey,
        shortcode as MetricShortcode,
        metricKey as MetricKey
      );
    } catch (error) {
      logger.error('Error finding metrics by ID', { id, error });
      throw error;
    }
  }

  /**
   * Finds all metrics for a project
   *
   * @param projectKey - The project key
   * @returns All quality metrics for the project
   */
  async findByProject(projectKey: ProjectKey): Promise<QualityMetrics[]> {
    try {
      logger.debug('Finding all metrics for project', { projectKey });

      const repositoryId = await this.getRepositoryId(projectKey);
      const apiMetrics = await this.client.getQualityMetrics(projectKey);

      const domainMetrics = QualityMetricsMapper.toDomainFromList(
        apiMetrics,
        projectKey,
        repositoryId
      );

      logger.debug('Metrics found for project', {
        projectKey,
        count: domainMetrics.length,
      });

      return domainMetrics;
    } catch (error) {
      logger.error('Error finding metrics by project', { projectKey, error });
      throw error;
    }
  }

  /**
   * Finds metrics for a project with server-side filtering
   *
   * @param projectKey - The project key
   * @param shortcodeIn - Array of metric shortcodes to filter by
   * @returns Filtered quality metrics for the project
   */
  async findByProjectWithFilter(
    projectKey: ProjectKey,
    shortcodeIn: MetricShortcode[]
  ): Promise<QualityMetrics[]> {
    try {
      logger.debug('Finding metrics for project with filter', { projectKey, shortcodeIn });

      const repositoryId = await this.getRepositoryId(projectKey);
      // This now uses server-side filtering via the optimized client
      const apiMetrics = await this.client.getQualityMetrics(projectKey, { shortcodeIn });

      const domainMetrics = QualityMetricsMapper.toDomainFromList(
        apiMetrics,
        projectKey,
        repositoryId
      );

      logger.debug('Filtered metrics found for project', {
        projectKey,
        count: domainMetrics.length,
        requestedShortcodes: shortcodeIn.length,
      });

      return domainMetrics;
    } catch (error) {
      logger.error('Error finding filtered metrics by project', { projectKey, shortcodeIn, error });
      throw error;
    }
  }

  /**
   * Finds metrics by project and metric type
   *
   * @param projectKey - The project key
   * @param shortcode - The metric shortcode
   * @param metricKey - Optional metric key filter
   * @returns The metrics if found, null otherwise
   */
  async findByProjectAndMetric(
    projectKey: ProjectKey,
    shortcode: MetricShortcode,
    metricKey?: MetricKey
  ): Promise<QualityMetrics | null> {
    try {
      logger.debug('Finding metrics by project and type', {
        projectKey,
        shortcode,
        metricKey,
      });

      const repositoryId = await this.getRepositoryId(projectKey);
      const apiMetrics = await this.client.getQualityMetrics(projectKey, {
        shortcodeIn: [shortcode],
      });

      const metric = apiMetrics.find((m) => m.shortcode === shortcode);
      if (!metric) {
        logger.debug('Metric not found', { projectKey, shortcode });
        return null;
      }

      // If metricKey is specified, find that specific item
      if (metricKey) {
        const item = metric.items.find((i) => i.key === metricKey);
        if (!item) {
          logger.debug('Metric item not found', { projectKey, shortcode, metricKey });
          return null;
        }
        return QualityMetricsMapper.toDomain(metric, item, projectKey, repositoryId);
      }

      // If no metricKey specified, return the first item (usually AGGREGATE)
      if (metric.items.length > 0 && metric.items[0]) {
        return QualityMetricsMapper.toDomain(metric, metric.items[0], projectKey, repositoryId);
      }

      logger.debug('No metric items found', { projectKey, shortcode });
      return null;
    } catch (error) {
      logger.error('Error finding metrics by project and type', {
        projectKey,
        shortcode,
        metricKey,
        error,
      });
      throw error;
    }
  }

  /**
   * Finds all metrics with failing thresholds for a project
   *
   * @param projectKey - The project key
   * @returns Metrics that are currently failing their thresholds
   */
  async findFailingMetrics(projectKey: ProjectKey): Promise<QualityMetrics[]> {
    try {
      logger.debug('Finding failing metrics for project', { projectKey });

      const allMetrics = await this.findByProject(projectKey);
      const failingMetrics = allMetrics.filter((m) => m.thresholdStatus === 'FAILING');

      logger.debug('Failing metrics found', {
        projectKey,
        count: failingMetrics.length,
      });

      return failingMetrics;
    } catch (error) {
      logger.error('Error finding failing metrics', { projectKey, error });
      throw error;
    }
  }

  /**
   * Finds all reported metrics for a project
   *
   * @param projectKey - The project key
   * @returns Metrics that have isReported set to true
   */
  async findReportedMetrics(projectKey: ProjectKey): Promise<QualityMetrics[]> {
    try {
      logger.debug('Finding reported metrics for project', { projectKey });

      const allMetrics = await this.findByProject(projectKey);
      const reportedMetrics = allMetrics.filter((m) => m.configuration.isReported);

      logger.debug('Reported metrics found', {
        projectKey,
        count: reportedMetrics.length,
      });

      return reportedMetrics;
    } catch (error) {
      logger.error('Error finding reported metrics', { projectKey, error });
      throw error;
    }
  }

  /**
   * Finds metrics by composite ID components
   *
   * @param id - The composite ID components
   * @returns The metrics if found, null otherwise
   */
  async findByCompositeId(id: QualityMetricsId): Promise<QualityMetrics | null> {
    try {
      logger.debug('Finding metrics by composite ID components', { id });

      return this.findByProjectAndMetric(id.projectKey, id.shortcode, id.metricKey);
    } catch (error) {
      logger.error('Error finding metrics by composite ID', { id, error });
      throw error;
    }
  }

  /**
   * Counts total metrics for a project
   *
   * @param projectKey - The project key
   * @returns The total number of metrics
   */
  async countByProject(projectKey: ProjectKey): Promise<number> {
    try {
      logger.debug('Counting metrics for project', { projectKey });

      const metrics = await this.findByProject(projectKey);
      const count = metrics.length;

      logger.debug('Metric count for project', { projectKey, count });
      return count;
    } catch (error) {
      logger.error('Error counting metrics', { projectKey, error });
      throw error;
    }
  }

  /**
   * Counts failing metrics for a project
   *
   * @param projectKey - The project key
   * @returns The number of failing metrics
   */
  async countFailingByProject(projectKey: ProjectKey): Promise<number> {
    try {
      logger.debug('Counting failing metrics for project', { projectKey });

      const failingMetrics = await this.findFailingMetrics(projectKey);
      const count = failingMetrics.length;

      logger.debug('Failing metric count', { projectKey, count });
      return count;
    } catch (error) {
      logger.error('Error counting failing metrics', { projectKey, error });
      throw error;
    }
  }

  /**
   * Checks if metrics exist for a project and shortcode
   *
   * @param projectKey - The project key
   * @param shortcode - The metric shortcode
   * @returns True if metrics exist, false otherwise
   */
  async exists(projectKey: ProjectKey, shortcode: MetricShortcode): Promise<boolean> {
    try {
      logger.debug('Checking if metrics exist', { projectKey, shortcode });

      const metric = await this.findByProjectAndMetric(projectKey, shortcode);
      const exists = metric !== null;

      logger.debug('Metrics existence check', { projectKey, shortcode, exists });
      return exists;
    } catch (error) {
      logger.error('Error checking metrics existence', { projectKey, shortcode, error });
      throw error;
    }
  }

  /**
   * Saves quality metrics
   *
   * Note: The DeepSource API only supports updating thresholds and settings,
   * not creating new metrics. This method updates the threshold if needed.
   *
   * @param metrics - The metrics to save
   * @throws Error if the metric doesn't exist or update fails
   */
  async save(metrics: QualityMetrics): Promise<void> {
    logger.debug('Attempting to save metrics', {
      id: metrics.id,
      projectKey: metrics.projectKey,
    });

    try {
      // Update threshold if it has changed
      const threshold = metrics.configuration.threshold;
      const thresholdValue = threshold ? threshold.value : null;

      await this.client.setMetricThreshold({
        repositoryId: metrics.repositoryId,
        metricShortcode: metrics.configuration.shortcode,
        metricKey: metrics.configuration.metricKey,
        thresholdValue,
      });

      // Update settings
      await this.client.updateMetricSetting({
        repositoryId: metrics.repositoryId,
        metricShortcode: metrics.configuration.shortcode,
        isReported: metrics.configuration.isReported,
        isThresholdEnforced: metrics.configuration.isThresholdEnforced,
      });

      logger.debug('Metrics saved successfully', { id: metrics.id });
    } catch (error) {
      logger.error('Error saving metrics', { id: metrics.id, error });
      throw error;
    }
  }

  /**
   * Deletes quality metrics
   *
   * Note: The DeepSource API doesn't support deleting metrics.
   * This method is implemented to satisfy the interface but will
   * throw an error indicating the operation is not supported.
   *
   * @param id - The metrics ID to delete
   * @throws Error indicating the operation is not supported
   */
  // skipcq: JS-0105 - Repository method required by interface contract
  async delete(id: string): Promise<void> {
    logger.warn('Attempted to delete metrics', { id });

    throw new Error(
      'Delete operation is not supported by DeepSource API. ' +
        'Metrics are managed automatically based on repository configuration.'
    );
  }
}
