/**
 * @fileoverview ComplianceReport aggregate root
 *
 * This module defines the ComplianceReport aggregate which represents security
 * and code quality compliance reports for a project.
 */

import { AggregateRoot } from '../../shared/aggregate-root.js';
import { ProjectKey, GraphQLNodeId } from '../../../types/branded.js';
import { ReportType } from '../../../types/report-types.js';
import { IssueCount } from '../../value-objects/issue-count.js';
import { createLogger } from '../../../utils/logging/logger.js';
import {
  ComplianceReportId,
  ComplianceReportStatus,
  ComplianceCategory,
  ComplianceSummary,
  ComplianceScore,
  SeverityDistribution,
  ReportTrend,
  CreateComplianceReportParams,
  UpdateCategoriesParams,
  UpdateTrendParams,
} from './compliance-report.types.js';

const logger = createLogger('ComplianceReport');

/**
 * ComplianceReport aggregate root
 *
 * Represents compliance reports (OWASP, SANS, MISRA-C) for a project.
 * Tracks compliance categories, issues, and trends over time.
 *
 * @example
 * ```typescript
 * const report = ComplianceReport.create({
 *   projectKey: asProjectKey('my-project'),
 *   repositoryId: asGraphQLNodeId('repo123'),
 *   reportType: ReportType.OWASP_TOP_10,
 *   categories: [
 *     {
 *       name: 'A1: Injection',
 *       compliant: IssueCount.create(45),
 *       nonCompliant: IssueCount.create(5),
 *       issueCount: IssueCount.create(50),
 *       severity: 'CRITICAL'
 *     }
 *   ]
 * });
 *
 * console.log(report.summary.complianceScore.value); // 90
 * console.log(report.isCompliant); // true
 * ```
 */
export class ComplianceReport extends AggregateRoot<string> {
  private _projectKey: ProjectKey;
  private _repositoryId: GraphQLNodeId;
  private _reportType: ReportType;
  private _status: ComplianceReportStatus;
  private _categories: ComplianceCategory[];
  private _summary: ComplianceSummary;
  private _trend: ReportTrend | null;
  private _generatedAt: Date;
  private _lastUpdated: Date;

  private constructor(
    id: string,
    projectKey: ProjectKey,
    repositoryId: GraphQLNodeId,
    reportType: ReportType,
    status: ComplianceReportStatus,
    categories: ComplianceCategory[],
    summary: ComplianceSummary,
    trend: ReportTrend | null,
    generatedAt: Date,
    lastUpdated: Date
  ) {
    super(id);
    this._projectKey = projectKey;
    this._repositoryId = repositoryId;
    this._reportType = reportType;
    this._status = status;
    this._categories = categories;
    this._summary = summary;
    this._trend = trend;
    this._generatedAt = generatedAt;
    this._lastUpdated = lastUpdated;
  }

  /**
   * Creates a new ComplianceReport aggregate
   *
   * @param params - Creation parameters
   * @returns A new ComplianceReport instance
   */
  static create(params: CreateComplianceReportParams): ComplianceReport {
    const { projectKey, repositoryId, reportType, status, categories, trend } = params;

    // Create composite ID
    const id = ComplianceReport.createId({ projectKey, reportType });

    const now = new Date();
    const initialCategories = categories || [];
    const summary = ComplianceReport.calculateSummary(initialCategories);

    const report = new ComplianceReport(
      id,
      projectKey,
      repositoryId,
      reportType,
      status || 'PENDING',
      initialCategories,
      summary,
      trend || null,
      now,
      now
    );

    report.addDomainEvent({
      aggregateId: id,
      eventType: 'ComplianceReportCreated',
      occurredAt: now,
      payload: {
        projectKey,
        reportType,
        complianceScore: summary.complianceScore.value,
      },
    });

    return report;
  }

  /**
   * Creates a composite ID for the aggregate
   */
  private static createId(id: ComplianceReportId): string {
    return `${id.projectKey}:${id.reportType}`;
  }

  /**
   * Calculates summary statistics from categories
   */
  private static calculateSummary(categories: ComplianceCategory[]): ComplianceSummary {
    let totalCompliant = IssueCount.zero();
    let totalNonCompliant = IssueCount.zero();
    let totalIssues = IssueCount.zero();

    const severityDistribution: SeverityDistribution = {
      critical: IssueCount.zero('critical'),
      major: IssueCount.zero('major'),
      minor: IssueCount.zero('minor'),
      info: IssueCount.zero('info'),
    };

    for (const category of categories) {
      totalCompliant = totalCompliant.add(category.compliant);
      totalNonCompliant = totalNonCompliant.add(category.nonCompliant);
      totalIssues = totalIssues.add(category.issueCount);

      // Update severity distribution
      switch (category.severity) {
        case 'CRITICAL':
          severityDistribution.critical = severityDistribution.critical.add(category.nonCompliant);
          break;
        case 'MAJOR':
          severityDistribution.major = severityDistribution.major.add(category.nonCompliant);
          break;
        case 'MINOR':
          severityDistribution.minor = severityDistribution.minor.add(category.nonCompliant);
          break;
        case 'INFO':
          severityDistribution.info = severityDistribution.info.add(category.nonCompliant);
          break;
        default:
          // Log unexpected severity level but continue processing
          logger.warn(`Unexpected severity level: ${category.severity}`);
          break;
      }
    }

    // Calculate compliance score
    const total = totalCompliant.count + totalNonCompliant.count;
    const scoreValue = total > 0 ? (totalCompliant.count / total) * 100 : 100;

    const complianceScore: ComplianceScore = {
      value: Math.round(scoreValue * 10) / 10, // Round to 1 decimal
      level: ComplianceReport.getComplianceLevel(scoreValue),
    };

    return {
      totalCompliant,
      totalNonCompliant,
      totalIssues,
      complianceScore,
      severityDistribution,
    };
  }

  /**
   * Determines compliance level based on score
   */
  private static getComplianceLevel(score: number): ComplianceScore['level'] {
    if (score >= 95) return 'EXCELLENT';
    if (score >= 85) return 'GOOD';
    if (score >= 70) return 'FAIR';
    return 'POOR';
  }

  /**
   * Gets the project key
   */
  get projectKey(): ProjectKey {
    return this._projectKey;
  }

  /**
   * Gets the repository ID
   */
  get repositoryId(): GraphQLNodeId {
    return this._repositoryId;
  }

  /**
   * Gets the report type
   */
  get reportType(): ReportType {
    return this._reportType;
  }

  /**
   * Gets the report status
   */
  get status(): ComplianceReportStatus {
    return this._status;
  }

  /**
   * Gets the compliance categories
   */
  get categories(): ReadonlyArray<ComplianceCategory> {
    return [...this._categories];
  }

  /**
   * Gets the summary statistics
   */
  get summary(): Readonly<ComplianceSummary> {
    return {
      ...this._summary,
      severityDistribution: { ...this._summary.severityDistribution },
    };
  }

  /**
   * Gets the trend information
   */
  get trend(): Readonly<ReportTrend> | null {
    return this._trend ? { ...this._trend } : null;
  }

  /**
   * Gets the generation timestamp
   */
  get generatedAt(): Date {
    return this._generatedAt;
  }

  /**
   * Gets the last update timestamp
   */
  get lastUpdated(): Date {
    return this._lastUpdated;
  }

  /**
   * Checks if the report meets compliance standards
   */
  get isCompliant(): boolean {
    return this._summary.complianceScore.value >= 85; // 85% is the default threshold
  }

  /**
   * Checks if the report has critical issues
   */
  get hasCriticalIssues(): boolean {
    return this._summary.severityDistribution.critical.isPositive;
  }

  /**
   * Generates/regenerates the report
   */
  generate(): void {
    if (this._status === 'GENERATING') {
      throw new Error('Report is already being generated');
    }

    this._status = 'GENERATING';
    this._lastUpdated = new Date();

    this.addDomainEvent({
      aggregateId: this._id,
      eventType: 'ComplianceReportGenerationStarted',
      occurredAt: this._lastUpdated,
      payload: {},
    });

    this.markAsModified();
  }

  /**
   * Completes report generation
   */
  complete(): void {
    if (this._status !== 'GENERATING') {
      throw new Error('Report must be in GENERATING status to complete');
    }

    this._status = 'READY';
    this._generatedAt = new Date();
    this._lastUpdated = this._generatedAt;

    this.addDomainEvent({
      aggregateId: this._id,
      eventType: 'ComplianceReportCompleted',
      occurredAt: this._lastUpdated,
      payload: {
        complianceScore: this._summary.complianceScore.value,
        hasCriticalIssues: this.hasCriticalIssues,
      },
    });

    this.markAsModified();
  }

  /**
   * Fails report generation
   *
   * @param reason - The reason for failure
   */
  fail(reason: string): void {
    if (this._status !== 'GENERATING') {
      throw new Error('Report must be in GENERATING status to fail');
    }

    this._status = 'ERROR';
    this._lastUpdated = new Date();

    this.addDomainEvent({
      aggregateId: this._id,
      eventType: 'ComplianceReportFailed',
      occurredAt: this._lastUpdated,
      payload: { reason },
    });

    this.markAsModified();
  }

  /**
   * Updates the report categories
   *
   * @param params - Category update parameters
   */
  updateCategories(params: UpdateCategoriesParams): void {
    const { categories } = params;

    if (categories.length === 0) {
      throw new Error('Report must have at least one category');
    }

    this._categories = categories;
    this._summary = ComplianceReport.calculateSummary(categories);
    this._lastUpdated = new Date();

    this.addDomainEvent({
      aggregateId: this._id,
      eventType: 'ComplianceReportCategoriesUpdated',
      occurredAt: this._lastUpdated,
      payload: {
        categoryCount: categories.length,
        complianceScore: this._summary.complianceScore.value,
      },
    });

    this.markAsModified();
  }

  /**
   * Updates the report trend
   *
   * @param params - Trend update parameters
   */
  updateTrend(params: UpdateTrendParams): void {
    const { trend } = params;

    this._trend = trend;
    this._lastUpdated = new Date();

    this.addDomainEvent({
      aggregateId: this._id,
      eventType: 'ComplianceReportTrendUpdated',
      occurredAt: this._lastUpdated,
      payload: {
        trendDirection: trend.direction,
        changePercentage: trend.changePercentage,
      },
    });

    this.markAsModified();
  }

  /**
   * Compares this report with a previous report
   *
   * @param previous - The previous report to compare with
   * @returns Trend information
   */
  compareWithPrevious(previous: ComplianceReport): ReportTrend {
    if (previous._reportType !== this._reportType) {
      throw new Error('Cannot compare reports of different types');
    }

    const currentScore = this._summary.complianceScore.value;
    const previousScore = previous._summary.complianceScore.value;
    const changePercentage = currentScore - previousScore;

    let direction: ReportTrend['direction'];
    if (Math.abs(changePercentage) < 0.1) {
      direction = 'STABLE';
    } else if (changePercentage > 0) {
      direction = 'IMPROVING';
    } else {
      direction = 'DEGRADING';
    }

    return {
      label: 'Compared to previous report',
      value: currentScore,
      changePercentage,
      direction,
    };
  }

  /**
   * Reconstructs ComplianceReport from persisted data
   *
   * @param data - Persisted report data
   * @returns A reconstructed ComplianceReport instance
   */
  static fromPersistence(data: {
    id: string;
    projectKey: ProjectKey;
    repositoryId: GraphQLNodeId;
    reportType: ReportType;
    status: ComplianceReportStatus;
    categories: ComplianceCategory[];
    summary: ComplianceSummary;
    trend: ReportTrend | null;
    generatedAt: Date;
    lastUpdated: Date;
  }): ComplianceReport {
    return new ComplianceReport(
      data.id,
      data.projectKey,
      data.repositoryId,
      data.reportType,
      data.status,
      data.categories,
      data.summary,
      data.trend,
      data.generatedAt,
      data.lastUpdated
    );
  }

  /**
   * Converts the report to a persistence-friendly format
   */
  toPersistence(): {
    id: string;
    projectKey: ProjectKey;
    repositoryId: GraphQLNodeId;
    reportType: ReportType;
    status: ComplianceReportStatus;
    categories: ComplianceCategory[];
    summary: ComplianceSummary;
    trend: ReportTrend | null;
    generatedAt: Date;
    lastUpdated: Date;
  } {
    return {
      id: this._id,
      projectKey: this._projectKey,
      repositoryId: this._repositoryId,
      reportType: this._reportType,
      status: this._status,
      categories: [...this._categories],
      summary: this.summary,
      trend: this._trend,
      generatedAt: this._generatedAt,
      lastUpdated: this._lastUpdated,
    };
  }
}
