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,
  normalizeCatalogLocale,
} from '../catalog/catalog-label.service';

export type TreatmentCatalogContext = 'appointment' | 'treatment';

export type TreatmentTypeCatalogEntry = {
  id: string;
  code: string;
  labDependent: boolean;
  sortOrder: number;
  label: string;
  availableInAppointments: boolean;
  availableInTreatment: boolean;
};

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

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

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

  async refresh(): Promise<void> {
    const rows = await this.prisma.treatmentType.findMany({
      where: { isActive: true },
      orderBy: [{ sortOrder: 'asc' }, { code: 'asc' }],
      select: {
        id: true,
        code: true,
        labDependent: true,
        sortOrder: true,
        availableInAppointments: true,
        availableInTreatment: true,
      },
    });

    this.byCode = new Map(
      rows.map((row) => [
        row.code,
        {
          id: row.id,
          code: row.code,
          labDependent: row.labDependent,
          sortOrder: row.sortOrder,
          label: row.code,
          availableInAppointments: row.availableInAppointments,
          availableInTreatment: row.availableInTreatment,
        },
      ]),
    );
    this.loaded = true;
  }

  async list(
    localeInput?: string | null,
    context?: TreatmentCatalogContext | null,
  ): Promise<TreatmentTypeCatalogEntry[]> {
    await this.ensureLabels(localeInput);
    return [...this.byCode.values()]
      .filter((entry) => {
        if (context === 'appointment') return entry.availableInAppointments;
        if (context === 'treatment') return entry.availableInTreatment;
        return true;
      })
      .sort((a, b) => a.sortOrder - b.sortOrder || a.code.localeCompare(b.code));
  }

  private async ensureLabels(localeInput?: string | null) {
    this.ensureLoaded();
    const locale = normalizeCatalogLocale(localeInput);
    const codes = [...this.byCode.keys()];
    const labels = await this.catalogLabels.resolveLabels(
      CatalogEntityKind.TREATMENT_TYPE,
      codes,
      locale,
    );

    for (const [code, entry] of this.byCode) {
      entry.label = labels.get(code) ?? entry.code;
    }
  }

  getByCode(code: string): TreatmentTypeCatalogEntry | undefined {
    this.ensureLoaded();
    return this.byCode.get(code);
  }

  assertKnownTreatmentType(code: string): TreatmentTypeCatalogEntry {
    const entry = this.getByCode(code);
    if (!entry) {
      throw new AppException(ErrorCode.CATALOG_UNKNOWN_TREATMENT_TYPE, HttpStatus.BAD_REQUEST);
    }
    return entry;
  }

  assertLabDependentTreatmentType(code: string): TreatmentTypeCatalogEntry {
    const entry = this.assertKnownTreatmentType(code);
    if (!entry.labDependent) {
      throw new AppException(ErrorCode.CATALOG_TREATMENT_NOT_LAB_DEPENDENT, HttpStatus.BAD_REQUEST);
    }
    return entry;
  }

  isLabDependent(code: string): boolean {
    return this.getByCode(code)?.labDependent ?? false;
  }

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