/**
 * @fileoverview Security client for the DeepSource API
 * This module provides functionality for working with DeepSource security features.
 */

import { BaseDeepSourceClient } from './base-client.js';
import {
  VulnerabilityOccurrence,
  VulnerabilitySeverity,
  Vulnerability,
} from '../models/security.js';
import { PaginatedResponse, PaginationParams } from '../utils/pagination/types.js';
import { isErrorWithMessage } from '../utils/errors/handlers.js';
import { ReportType } from '../deepsource.js';

/**
 * Compliance report data structure
 * @public
 */
export interface ComplianceReport {
  reportType: ReportType;
  status: 'PASSING' | 'FAILING' | 'NOOP';
  title: string;
  description: string;
  severityDistribution: {
    critical: number;
    major: number;
    minor: number;
    total: number;
  };
  trend?: {
    label?: string;
    value?: number;
    changePercentage?: number;
  };
  categories: Array<{
    name: string;
    status: 'PASSING' | 'FAILING' | 'NOOP';
    issueCount: number;
    description?: string;
  }>;
  complianceScore: number;
  lastUpdated: string;
}

/**
 * Client for interacting with DeepSource security API
 * @class
 * @extends BaseDeepSourceClient
 * @public
 */
export class SecurityClient extends BaseDeepSourceClient {
  /**
   * Fetches dependency vulnerabilities from a DeepSource project
   * @param projectKey The project key to fetch vulnerabilities for
   * @param params Optional pagination parameters
   * @returns Promise that resolves to a paginated list of vulnerability occurrences
   * @throws {ClassifiedError} When the API request fails
   * @public
   */
  async getDependencyVulnerabilities(
    projectKey: string,
    params: PaginationParams = {}
  ): Promise<PaginatedResponse<VulnerabilityOccurrence>> {
    try {
      this.logger.info('Fetching dependency vulnerabilities from DeepSource API', {
        projectKey,
      });

      const project = await this.findProjectByKey(projectKey);
      if (!project) {
        return BaseDeepSourceClient.createEmptyPaginatedResponse<VulnerabilityOccurrence>();
      }

      const normalizedParams = BaseDeepSourceClient.normalizePaginationParams(params);
      const query = SecurityClient.buildVulnerabilitiesQuery();

      const response = await this.executeGraphQL(query, {
        login: project.repository.login,
        name: project.repository.name,
        provider: project.repository.provider,
        ...normalizedParams,
      });

      if (!response.data) {
        throw new Error('No data received from GraphQL API');
      }

      const vulnerabilities = this.extractVulnerabilitiesFromResponse(response.data);

      this.logger.info('Successfully fetched dependency vulnerabilities', {
        count: vulnerabilities.length,
      });

      return {
        items: vulnerabilities,
        pageInfo: {
          hasNextPage: false, // Simplified for now
          hasPreviousPage: false,
        },
        totalCount: vulnerabilities.length,
      };
    } catch (error) {
      return this.handleVulnerabilitiesError(error);
    }
  }

  /**
   * Fetches a compliance report for a specific report type
   * @param projectKey The project key
   * @param reportType The type of compliance report
   * @returns Promise that resolves to compliance report if available, null otherwise
   * @public
   */
  async getComplianceReport(
    projectKey: string,
    reportType: ReportType
  ): Promise<ComplianceReport | null> {
    try {
      this.logger.info('Fetching compliance report from DeepSource API', {
        projectKey,
        reportType,
      });

      // Validate report type
      if (
        reportType !== ReportType.OWASP_TOP_10 &&
        reportType !== ReportType.SANS_TOP_25 &&
        reportType !== ReportType.MISRA_C
      ) {
        throw new Error(`Unsupported report type: ${reportType}`);
      }

      const project = await this.findProjectByKey(projectKey);
      if (!project) {
        return null;
      }

      const query = SecurityClient.buildComplianceReportQuery();
      const fieldName = SecurityClient.getReportFieldName(reportType);

      const response = await this.executeGraphQL(query, {
        login: project.repository.login,
        name: project.repository.name,
        provider: project.repository.provider,
      });

      if (!response.data) {
        return null;
      }

      const reportData = this.extractComplianceReportFromResponse(
        response.data,
        reportType,
        fieldName
      );

      if (reportData) {
        this.logger.info('Successfully fetched compliance report', {
          reportType,
          status: reportData.status,
        });
      }

      return reportData;
    } catch (error) {
      if (isErrorWithMessage(error, 'NoneType') || isErrorWithMessage(error, 'not found')) {
        return null;
      }
      throw error;
    }
  }

  /**
   * Builds GraphQL query for dependency vulnerabilities
   * @private
   */
  private static buildVulnerabilitiesQuery(): string {
    return `
      query getDependencyVulnerabilities(
        $login: String!
        $name: String!
        $provider: VCSProvider!
        $first: Int
        $after: String
      ) {
        repository(login: $login, name: $name, vcsProvider: $provider) {
          dependencyVulnerabilities(first: $first, after: $after) {
            edges {
              node {
                id
                package {
                  id
                  ecosystem
                  name
                }
                packageVersion {
                  id
                  version
                }
                vulnerability {
                  id
                  identifier
                  summary
                  details
                  severity
                  cvssV3BaseScore
                  cvssV2BaseScore
                  fixedVersions
                  aliases
                  referenceUrls
                }
              }
            }
            pageInfo {
              hasNextPage
              hasPreviousPage
              startCursor
              endCursor
            }
            totalCount
          }
        }
      }
    `;
  }

  /**
   * Builds GraphQL query for compliance reports
   * @private
   */
  private static buildComplianceReportQuery(): string {
    return `
      query getComplianceReports(
        $login: String!
        $name: String!
        $provider: VCSProvider!
      ) {
        repository(login: $login, name: $name, vcsProvider: $provider) {
          name
          id
          reports {
            owaspTop10 {
              status
              categories {
                name
                status
                criticalCount: count(severity: CRITICAL)
                majorCount: count(severity: MAJOR)
                minorCount: count(severity: MINOR)
                total: count
              }
            }
            sansTop25 {
              status
              categories {
                name
                status
                criticalCount: count(severity: CRITICAL)
                majorCount: count(severity: MAJOR)
                minorCount: count(severity: MINOR)
                total: count
              }
            }
            misraC {
              status
              categories {
                name
                status
                criticalCount: count(severity: CRITICAL)
                majorCount: count(severity: MAJOR)
                minorCount: count(severity: MINOR)
                total: count
              }
            }
          }
        }
      }
    `;
  }

  /**
   * Extracts vulnerabilities from GraphQL response
   * @private
   */
  private extractVulnerabilitiesFromResponse(responseData: unknown): VulnerabilityOccurrence[] {
    const vulnerabilities: VulnerabilityOccurrence[] = [];

    try {
      const repository = (responseData as Record<string, unknown>)?.repository as Record<
        string,
        unknown
      >;
      const depVulns = repository?.dependencyVulnerabilities as Record<string, unknown>;
      const vulnEdges = (depVulns?.edges ?? []) as Array<Record<string, unknown>>;

      for (const edge of vulnEdges) {
        const node = edge.node as Record<string, unknown>;
        if (SecurityClient.isValidVulnerabilityNode(node)) {
          vulnerabilities.push(SecurityClient.mapVulnerabilityOccurrence(node));
        }
      }
    } catch (error) {
      this.logger.error('Error extracting vulnerabilities from response', { error });
    }

    return vulnerabilities;
  }

  /**
   * Validates if a vulnerability node has required structure
   * @private
   */
  private static isValidVulnerabilityNode(node: unknown): boolean {
    if (!node || typeof node !== 'object') {
      return false;
    }

    const record = node as Record<string, unknown>;

    return Boolean(record.id && record.package && record.packageVersion && record.vulnerability);
  }

  /**
   * Maps a vulnerability node to VulnerabilityOccurrence
   * @private
   */
  private static mapVulnerabilityOccurrence(
    node: Record<string, unknown>
  ): VulnerabilityOccurrence {
    const packageInfo = (node.package as Record<string, unknown>) || {};
    const packageVersion = (node.packageVersion as Record<string, unknown>) || {};
    const vulnerability = (node.vulnerability as Record<string, unknown>) || {};

    return {
      id: String(node.id ?? ''),
      package: {
        id: String(packageInfo.id ?? ''),
        ecosystem: String(packageInfo.ecosystem ?? ''),
        name: String(packageInfo.name ?? ''),
      },
      packageVersion: {
        id: String(packageVersion.id ?? ''),
        version: String(packageVersion.version ?? ''),
      },
      vulnerability: (() => {
        const vuln: Record<string, unknown> = {
          id: String(vulnerability.id ?? ''),
          identifier: String(vulnerability.identifier ?? ''),
          aliases: Array.isArray(vulnerability.aliases) ? (vulnerability.aliases as string[]) : [],
          summary: String(vulnerability.summary ?? ''),
          details: String(vulnerability.details ?? ''),
          publishedAt: String(vulnerability.publishedAt ?? new Date().toISOString()),
          updatedAt: String(vulnerability.updatedAt ?? new Date().toISOString()),
          severity: String(vulnerability.severity ?? 'NONE') as VulnerabilitySeverity,
          introducedVersions: Array.isArray(vulnerability.introducedVersions)
            ? (vulnerability.introducedVersions as string[])
            : [],
          fixedVersions: Array.isArray(vulnerability.fixedVersions)
            ? (vulnerability.fixedVersions as string[])
            : [],
          referenceUrls: Array.isArray(vulnerability.referenceUrls)
            ? (vulnerability.referenceUrls as string[])
            : [],
        }; // Return vulnerability object

        if (vulnerability.cvssV3BaseScore) {
          vuln.cvssV3BaseScore = Number(vulnerability.cvssV3BaseScore);
        }

        if (vulnerability.cvssV2BaseScore) {
          vuln.cvssV2BaseScore = Number(vulnerability.cvssV2BaseScore);
        }

        return vuln as unknown as Vulnerability;
      })(),
      reachability: 'UNKNOWN' as const,
      fixability: 'UNFIXABLE' as const,
    };
  }

  /**
   * Extracts compliance report from GraphQL response
   * @private
   */
  private extractComplianceReportFromResponse(
    responseData: unknown,
    reportType: ReportType,
    fieldName: string
  ): ComplianceReport | null {
    try {
      const repository = (responseData as Record<string, unknown>)?.repository as Record<
        string,
        unknown
      >;
      const reports = repository?.reports as Record<string, unknown>;
      const reportData = reports?.[fieldName] as Record<string, unknown>;

      if (!reportData) {
        return null;
      }

      const categories = (reportData.categories ?? []) as Array<Record<string, unknown>>;
      const status = String(reportData.status ?? 'NOOP') as 'PASSING' | 'FAILING' | 'NOOP';

      // Calculate severity distribution
      let totalCritical = 0;
      let totalMajor = 0;
      let totalMinor = 0;
      let totalIssues = 0;

      const processedCategories = categories.map((category) => {
        const criticalCount = Number(category.criticalCount ?? 0);
        const majorCount = Number(category.majorCount ?? 0);
        const minorCount = Number(category.minorCount ?? 0);
        const total = Number(category.total ?? 0);

        totalCritical += criticalCount;
        totalMajor += majorCount;
        totalMinor += minorCount;
        totalIssues += total;

        return {
          name: String(category.name ?? ''),
          status: String(category.status ?? 'NOOP') as 'PASSING' | 'FAILING' | 'NOOP',
          issueCount: total,
          description: `${criticalCount} critical, ${majorCount} major, ${minorCount} minor issues`,
        };
      });

      // Calculate compliance score (0-100)
      const complianceScore =
        totalIssues === 0
          ? 100
          : Math.max(0, 100 - (totalCritical * 10 + totalMajor * 5 + totalMinor));

      return {
        reportType,
        status,
        title: SecurityClient.getReportTitle(reportType),
        description: `${reportType.replace(/_/g, ' ')} compliance analysis`,
        severityDistribution: {
          critical: totalCritical,
          major: totalMajor,
          minor: totalMinor,
          total: totalIssues,
        },
        categories: processedCategories,
        complianceScore: Math.round(complianceScore),
        lastUpdated: new Date().toISOString(),
      };
    } catch (error) {
      this.logger.error('Error extracting compliance report from response', { error });
      return null;
    }
  }

  /**
   * Gets the GraphQL field name for a report type
   * @private
   */
  private static getReportFieldName(reportType: ReportType): string {
    switch (reportType) {
      case ReportType.OWASP_TOP_10:
        return 'owaspTop10';
      case ReportType.SANS_TOP_25:
        return 'sansTop25';
      case ReportType.MISRA_C:
        return 'misraC';
      default:
        throw new Error(`Unsupported report type: ${reportType}`);
    }
  }

  /**
   * Gets the human-readable title for a report type
   * @private
   */
  private static getReportTitle(reportType: ReportType): string {
    switch (reportType) {
      case ReportType.OWASP_TOP_10:
        return 'OWASP Top 10';
      case ReportType.SANS_TOP_25:
        return 'SANS Top 25';
      case ReportType.MISRA_C:
        return 'MISRA C';
      default:
        return reportType.replace(/_/g, ' ');
    }
  }

  /**
   * Handles errors during vulnerabilities fetching
   * @private
   */
  private handleVulnerabilitiesError(error: unknown): PaginatedResponse<VulnerabilityOccurrence> {
    this.logger.error('Error in getDependencyVulnerabilities', {
      errorType: typeof error,
      errorMessage: error instanceof Error ? error.message : String(error),
    });

    if (isErrorWithMessage(error, 'NoneType')) {
      return {
        items: [],
        pageInfo: {
          hasNextPage: false,
          hasPreviousPage: false,
        },
        totalCount: 0,
      };
    }

    throw error;
  }
}
