import { HttpStatus, Injectable, OnModuleInit } from '@nestjs/common';
import { AppException, ErrorCode } from '../../common/errors';
import { CatalogEntityKind } from '@prisma/client';
import { PrismaService } from '../../../prisma/prisma.service';
import {
  CatalogLabelService,
  CatalogLocale,
  normalizeCatalogLocale,
} from '../catalog/catalog-label.service';

export type ProsthesisTypeCatalogEntry = {
  code: string;
  sortOrder: number;
  label: string;
  category: string;
  subcategory: string;
  chartRegion: string;
  stackGroup: string;
};

type CatalogRow = {
  sortOrder: number;
  category: string;
  subcategory: string;
  chartRegion: string;
  stackGroup: string;
};

@Injectable()
export class ProsthesisCatalogService implements OnModuleInit {
  private loaded = false;
  private byCode = new Map<string, CatalogRow>();

  constructor(
    private readonly prisma: PrismaService,
    private readonly catalogLabels: CatalogLabelService,
  ) {}

  async onModuleInit() {
    await this.refresh();
  }

  async refresh(): Promise<void> {
    const rows = await this.prisma.prosthesisType.findMany({
      where: { isActive: true },
      orderBy: [{ sortOrder: 'asc' }, { code: 'asc' }],
      select: {
        code: true,
        sortOrder: true,
        category: true,
        subcategory: true,
        chartRegion: true,
        stackGroup: true,
      },
    });

    this.byCode = new Map(
      rows.map((row) => [
        row.code,
        {
          sortOrder: row.sortOrder,
          category: row.category,
          subcategory: row.subcategory,
          chartRegion: row.chartRegion,
          stackGroup: row.stackGroup,
        },
      ]),
    );
    this.loaded = true;
  }

  async list(localeInput?: string | null): Promise<ProsthesisTypeCatalogEntry[]> {
    await this.refresh();
    const locale = normalizeCatalogLocale(localeInput);
    const codes = [...this.byCode.keys()];
    const labels = await this.catalogLabels.resolveLabels(
      CatalogEntityKind.PROSTHESIS_TYPE,
      codes,
      locale,
    );

    return codes
      .map((code) => {
        const row = this.byCode.get(code)!;
        return {
          code,
          sortOrder: row.sortOrder,
          label: labels.get(code) ?? code,
          category: row.category,
          subcategory: row.subcategory,
          chartRegion: row.chartRegion,
          stackGroup: row.stackGroup,
        };
      })
      .sort((a, b) => a.sortOrder - b.sortOrder || a.code.localeCompare(b.code));
  }

  assertKnownProsthesisType(code: string): void {
    this.ensureLoaded();
    if (!this.byCode.has(code)) {
      throw new AppException(ErrorCode.CATALOG_UNKNOWN_PROSTHESIS_TYPE, HttpStatus.BAD_REQUEST);
    }
  }

  async getStepCodesForProsthesisType(prosthesisTypeCode: string): Promise<string[]> {
    const type = await this.prisma.prosthesisType.findUnique({
      where: { code: prosthesisTypeCode },
      select: {
        steps: {
          orderBy: { stepOrder: 'asc' },
          select: { labWorkflowStep: { select: { code: true } } },
        },
      },
    });

    if (!type) {
      return [];
    }

    return type.steps.map((s) => s.labWorkflowStep.code);
  }

  async resolveStepLabels(stepCodes: string[], locale: CatalogLocale): Promise<Map<string, string>> {
    return this.catalogLabels.resolveLabels(
      CatalogEntityKind.LAB_WORKFLOW_STEP,
      stepCodes,
      locale,
    );
  }

  private ensureLoaded() {
    if (!this.loaded) {
      throw new Error('Prosthesis catalog is not loaded yet');
    }
  }
}
