import {
  HttpStatus,
  Injectable,
} from '@nestjs/common';
import { CatalogEntityKind, Prisma } from '@prisma/client';
import { PrismaService } from '../../../prisma/prisma.service';
import {
  buildLabCaseShareUrl,
  generateLabCaseAccessToken,
} from '../../common/lab-case-access-token';
import { AppException, ErrorCode } from '../../common/errors';
import { hasEffectivePermission } from '../../common/membership-permissions';
import {
  CatalogLabelService,
  normalizeCatalogLocale,
} from '../catalog/catalog-label.service';
import { isActorTreatmentProvider } from '../../common/treatment-provider-scope';
import { aggregateProsthesisGroupsFromTasks } from './lab-case-task.util';
import { isLabCaseOverdue } from '../../common/lab-case-due-date';
import {
  atomicProsthesisCodes,
  prosthesisGroupLabel,
} from '../../common/prosthesis-group';
import { TasksService } from '../tasks/tasks.service';
import { LabCaseCommentsService } from '../lab-case-comments/lab-case-comments.service';
import { CreateLabCaseCommentDto } from '../lab-case-comments/dto/lab-case-comment.dto';

const accessCaseInclude = {
  treatment: {
    select: {
      providerUserId: true,
      organization: { select: { id: true, name: true } },
      patient: { select: { id: true, firstName: true, lastName: true } },
      appointment: { select: { providerUserId: true } },
    },
  },
  sends: {
    orderBy: [{ sentAt: 'asc' as const }],
    include: { organization: { select: { id: true, name: true } } },
  },
  tasks: { select: { status: true } },
} satisfies Prisma.LabCaseInclude;

type ResolvedAccess =
  | {
      kind: 'lab';
      canEditTaskStatus: boolean;
      canPostComments: boolean;
      canToggleCommentVisibility: boolean;
    }
  | {
      kind: 'clinic';
      canEditTaskStatus: false;
      canPostComments: boolean;
      canToggleCommentVisibility: false;
    }
  | { kind: 'denied' };

@Injectable()
export class LabCaseAccessService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly catalogLabels: CatalogLabelService,
    private readonly tasksService: TasksService,
    private readonly commentsService: LabCaseCommentsService,
  ) {}

  async ensureAccessToken(labCaseId: string): Promise<string | null> {
    const labCase = await this.prisma.labCase.findUnique({
      where: { id: labCaseId },
      select: { sentAt: true, accessToken: true },
    });
    if (!labCase?.sentAt) return null;
    if (labCase.accessToken) return labCase.accessToken;

    const accessToken = generateLabCaseAccessToken();
    await this.prisma.labCase.update({
      where: { id: labCaseId },
      data: { accessToken },
    });
    return accessToken;
  }

  buildShareUrl(accessToken: string, locale?: string | null): string {
    return buildLabCaseShareUrl(accessToken, locale ?? 'en');
  }

  async resolveByToken(
    token: string,
    actorUserId: string,
    organizationId: string,
    localeInput?: string | null,
  ) {
    const labCase = await this.findCaseByToken(token);
    const access = await this.resolveAccess(labCase, actorUserId, organizationId);

    if (access.kind === 'denied') {
      throw new AppException(ErrorCode.LAB_CASE_ACCESS_DENIED, HttpStatus.FORBIDDEN);
    }

    const locale = normalizeCatalogLocale(localeInput);
    const prosthesisCodes = atomicProsthesisCodes(
      (
        await this.prisma.labCaseTask.findMany({
          where: { labCaseId: labCase.id },
          select: { prosthesisTypeCode: true },
        })
      ).map((t) => t.prosthesisTypeCode),
    );
    const prosthesisLabels = await this.catalogLabels.resolveLabels(
      CatalogEntityKind.PROSTHESIS_TYPE,
      prosthesisCodes,
      locale,
    );

    const prosthesisGroups = await this.buildProsthesisGroups(labCase.id, prosthesisLabels);

    return {
      success: true,
      data: {
        labCaseId: labCase.id,
        accessMode: access.kind,
        canEditTaskStatus: access.canEditTaskStatus,
        canPostComments: access.canPostComments,
        canToggleCommentVisibility: access.canToggleCommentVisibility,
        shareUrl: this.buildShareUrl(token, locale),
        sentAt: labCase.sentAt?.toISOString() ?? null,
        dueDate: labCase.dueDate?.toISOString() ?? null,
        isOverdue: isLabCaseOverdue(labCase.dueDate, labCase.tasks),
        isImportant: labCase.isImportant,
        clinic: labCase.treatment?.organization ?? {
          id: '',
          name: '',
        },
        lab: labCase.sends[0]?.organization ?? null,
        patient: labCase.treatment?.patient ?? {
          id: '',
          firstName: '',
          lastName: '',
        },
        prosthesisGroups,
      },
    };
  }

  async listTasksByToken(
    token: string,
    actorUserId: string,
    organizationId: string,
    localeInput?: string | null,
  ) {
    const labCase = await this.findCaseByToken(token);
    const access = await this.resolveAccess(labCase, actorUserId, organizationId);

    if (access.kind === 'denied') {
      throw new AppException(ErrorCode.LAB_CASE_ACCESS_DENIED, HttpStatus.FORBIDDEN);
    }

    return this.tasksService.listTasksForLabCaseId(labCase.id, localeInput);
  }

  async listCommentsByToken(
    token: string,
    actorUserId: string,
    organizationId: string,
  ) {
    const labCase = await this.findCaseByToken(token);
    const access = await this.resolveAccess(labCase, actorUserId, organizationId);

    if (access.kind === 'denied') {
      throw new AppException(ErrorCode.LAB_CASE_ACCESS_DENIED, HttpStatus.FORBIDDEN);
    }

    if (access.kind === 'lab') {
      return this.commentsService.listForLabViewer(labCase.id, organizationId, actorUserId);
    }

    return this.commentsService.listForClinicTreatmentCase(
      labCase.id,
      organizationId,
      actorUserId,
    );
  }

  async addCommentByToken(
    token: string,
    actorUserId: string,
    organizationId: string,
    dto: CreateLabCaseCommentDto,
  ) {
    const labCase = await this.findCaseByToken(token);
    const access = await this.resolveAccess(labCase, actorUserId, organizationId);

    if (access.kind === 'denied' || !access.canPostComments) {
      throw new AppException(ErrorCode.LAB_CASE_ACCESS_DENIED, HttpStatus.FORBIDDEN);
    }

    if (access.kind === 'lab') {
      return this.commentsService.addForLab(labCase.id, organizationId, actorUserId, dto);
    }

    return this.commentsService.addForClinicTreatmentCase(
      labCase.id,
      organizationId,
      actorUserId,
      dto,
    );
  }

  async setCommentVisibilityByToken(
    token: string,
    commentId: string,
    actorUserId: string,
    organizationId: string,
    visibleToClinic: boolean,
  ) {
    const labCase = await this.findCaseByToken(token);
    const access = await this.resolveAccess(labCase, actorUserId, organizationId);

    if (access.kind !== 'lab' || !access.canToggleCommentVisibility) {
      throw new AppException(ErrorCode.LAB_CASE_ACCESS_DENIED, HttpStatus.FORBIDDEN);
    }

    return this.commentsService.setVisibility(
      commentId,
      organizationId,
      actorUserId,
      visibleToClinic,
    );
  }

  private async findCaseByToken(token: string) {
    const labCase = await this.prisma.labCase.findFirst({
      where: { accessToken: token, sentAt: { not: null } },
      include: accessCaseInclude,
    });

    if (!labCase) {
      throw new AppException(ErrorCode.CASE_NOT_FOUND, HttpStatus.NOT_FOUND);
    }

    return labCase;
  }

  private async resolveAccess(
    labCase: Prisma.LabCaseGetPayload<{ include: typeof accessCaseInclude }>,
    actorUserId: string,
    organizationId: string,
  ): Promise<ResolvedAccess> {
    const clinicOrgId = labCase.treatment?.organization.id;
    const labOrgId =
      labCase.destinationOrganizationId ?? labCase.sends[0]?.organizationId ?? null;

    if (!labOrgId) {
      return { kind: 'denied' };
    }

    if (organizationId === labOrgId) {
      const membership = await this.getMembership(actorUserId, labOrgId);
      if (!membership) return { kind: 'denied' };
      if (
        hasEffectivePermission(membership, 'TAB_TASKS_READ') ||
        hasEffectivePermission(membership, 'TAB_TASKS_EDIT')
      ) {
        const canEdit = hasEffectivePermission(membership, 'TAB_TASKS_EDIT');
        return {
          kind: 'lab',
          canEditTaskStatus: canEdit,
          canPostComments: canEdit,
          canToggleCommentVisibility: canEdit,
        };
      }
      return { kind: 'denied' };
    }

    if (clinicOrgId && organizationId === clinicOrgId) {
      const membership = await this.getMembership(actorUserId, clinicOrgId);
      if (!membership) return { kind: 'denied' };
      if (!hasEffectivePermission(membership, 'TAB_TREATMENT_EDIT')) {
        return { kind: 'denied' };
      }
      if (!labCase.treatment || !isActorTreatmentProvider(labCase.treatment, actorUserId)) {
        return { kind: 'denied' };
      }
      return {
        kind: 'clinic',
        canEditTaskStatus: false,
        canPostComments: true,
        canToggleCommentVisibility: false,
      };
    }

    return { kind: 'denied' };
  }

  private async buildProsthesisGroups(
    labCaseId: string,
    prosthesisLabels: Map<string, string>,
  ) {
    const tasks = await this.prisma.labCaseTask.findMany({
      where: { labCaseId },
      select: { prosthesisTypeCode: true, teeth: true, selectionGroupId: true },
      orderBy: [{ prosthesisTypeCode: 'asc' }],
    });

    return aggregateProsthesisGroupsFromTasks(tasks).map((group) => ({
      ...group,
      prosthesisTypeLabel:
        prosthesisGroupLabel(group.prosthesisTypeCode, prosthesisLabels),
    }));
  }

  private async getMembership(userId: string, organizationId: string) {
    return this.prisma.membership.findFirst({
      where: {
        userId,
        organizationId,
        OR: [{ isOwner: true }, { isActive: true }],
      },
      include: {
        permissions: { include: { permission: true } },
        organization: { include: { type: true, plan: true } },
      },
    });
  }
}
