import {
  HttpStatus,
  Injectable,
} from '@nestjs/common';
import { AppException, ErrorCode } from '../../common/errors';
import { LabCaseCommentSide, LabCaseActivityType, LabCaseOrigin, Prisma, UserNotificationType } from '@prisma/client';
import { PrismaService } from '../../../prisma/prisma.service';
import { CreateLabCaseCommentDto } from './dto/lab-case-comment.dto';
import { hasEffectivePermission } from '../../common/membership-permissions';
import { treatmentProviderScopeWhere } from '../../common/treatment-provider-scope';
import { LabCaseActivityService } from '../notifications/lab-case-activity.service';
import { UserNotificationService } from '../notifications/user-notification.service';

const commentInclude = {
  authorUser: { select: { id: true, name: true } },
  authorOrganization: { select: { id: true, name: true } },
} satisfies Prisma.LabCaseCommentInclude;

type CommentWithRelations = Prisma.LabCaseCommentGetPayload<{
  include: typeof commentInclude;
}>;

@Injectable()
export class LabCaseCommentsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly labCaseActivity: LabCaseActivityService,
    private readonly userNotifications: UserNotificationService,
  ) {}

  // ---------- Lab side (TAB_TASKS_EDIT) ----------

  async listForLab(caseId: string, labOrganizationId: string, actorUserId: string) {
    await this.assertLabCanComment(caseId, labOrganizationId, actorUserId);
    const comments = await this.fetchComments(caseId);
    return { success: true, data: comments.map((c) => this.mapComment(c, LabCaseCommentSide.LAB)) };
  }

  async listForLabViewer(caseId: string, labOrganizationId: string, actorUserId: string) {
    await this.assertLabCanViewCase(caseId, labOrganizationId, actorUserId);
    const comments = await this.fetchComments(caseId);
    return { success: true, data: comments.map((c) => this.mapComment(c, LabCaseCommentSide.LAB)) };
  }

  async addForLab(
    caseId: string,
    labOrganizationId: string,
    actorUserId: string,
    dto: CreateLabCaseCommentDto,
  ) {
    await this.assertLabCanComment(caseId, labOrganizationId, actorUserId);
    const created = await this.prisma.labCaseComment.create({
      data: {
        labCaseId: caseId,
        authorUserId: actorUserId,
        authorOrganizationId: labOrganizationId,
        authorSide: LabCaseCommentSide.LAB,
        body: dto.body.trim(),
        visibleToClinic: dto.visibleToClinic ?? false,
      },
      include: commentInclude,
    });
    await this.labCaseActivity.record({
      labCaseId: caseId,
      type: LabCaseActivityType.LAB_COMMENT,
      actorUserId,
      payload: {
        commentId: created.id,
        visibleToClinic: created.visibleToClinic,
      },
    });

    void this.userNotifications.notify({
      organizationId: labOrganizationId,
      type: UserNotificationType.LAB_COMMENT,
      href: `/cases?caseId=${encodeURIComponent(caseId)}`,
      actorUserId,
      payload: { labCaseId: caseId, commentId: created.id },
      requiredPermission: 'TAB_TASKS_READ',
    });

    if (created.visibleToClinic) {
      const clinicOrgId = await this.clinicOrgIdForCase(caseId);
      if (clinicOrgId) {
        void this.userNotifications.notify({
          organizationId: clinicOrgId,
          type: UserNotificationType.LAB_COMMENT_CLINIC,
          href: `/treatment?labCaseId=${encodeURIComponent(caseId)}`,
          actorUserId,
          payload: { labCaseId: caseId, commentId: created.id },
          requiredPermission: 'TAB_TREATMENT_READ',
          labCaseIdForProviderScope: caseId,
        });
      }
    }

    return { success: true, data: this.mapComment(created, LabCaseCommentSide.LAB) };
  }

  async setVisibility(
    commentId: string,
    labOrganizationId: string,
    actorUserId: string,
    visibleToClinic: boolean,
  ) {
    const comment = await this.prisma.labCaseComment.findUnique({
      where: { id: commentId },
      select: { id: true, labCaseId: true, authorSide: true },
    });
    if (!comment) {
      throw new AppException(ErrorCode.COMMENT_NOT_FOUND, HttpStatus.NOT_FOUND);
    }
    await this.assertLabCanComment(comment.labCaseId, labOrganizationId, actorUserId);
    if (comment.authorSide !== LabCaseCommentSide.LAB) {
      throw new AppException(ErrorCode.COMMENT_VISIBILITY_LAB_ONLY, HttpStatus.FORBIDDEN);
    }
    const updated = await this.prisma.labCaseComment.update({
      where: { id: commentId },
      data: { visibleToClinic },
      include: commentInclude,
    });
    return { success: true, data: this.mapComment(updated, LabCaseCommentSide.LAB) };
  }

  // ---------- Clinic side (connection history) ----------

  async listForClinic(caseId: string, clinicOrganizationId: string) {
    await this.assertClinicOwnsCase(caseId, clinicOrganizationId, { requireSent: true });
    const comments = await this.fetchCommentsForClinicViewer(caseId);
    return {
      success: true,
      data: comments.map((c) => this.mapComment(c, LabCaseCommentSide.CLINIC)),
    };
  }

  async addForClinic(
    caseId: string,
    clinicOrganizationId: string,
    actorUserId: string,
    dto: CreateLabCaseCommentDto,
  ) {
    await this.assertClinicOwnsCase(caseId, clinicOrganizationId, { requireSent: true });
    const created = await this.prisma.labCaseComment.create({
      data: {
        labCaseId: caseId,
        authorUserId: actorUserId,
        authorOrganizationId: clinicOrganizationId,
        authorSide: LabCaseCommentSide.CLINIC,
        body: dto.body.trim(),
        visibleToClinic: true,
      },
      include: commentInclude,
    });
    await this.labCaseActivity.record({
      labCaseId: caseId,
      type: LabCaseActivityType.CLINIC_COMMENT,
      actorUserId,
      payload: { commentId: created.id },
    });
    const labOrgId = await this.labOrgIdForCase(caseId);
    if (labOrgId) {
      void this.userNotifications.notify({
        organizationId: labOrgId,
        type: UserNotificationType.CLINIC_COMMENT,
        href: `/cases?caseId=${encodeURIComponent(caseId)}`,
        actorUserId,
        payload: { labCaseId: caseId, commentId: created.id },
        requiredPermission: 'TAB_CASES_READ',
      });
    }
    return { success: true, data: this.mapComment(created, LabCaseCommentSide.CLINIC) };
  }

  // ---------- Clinic side (treatment dispatch — unsent cases allowed) ----------

  async listForClinicTreatmentCase(
    caseId: string,
    clinicOrganizationId: string,
    actorUserId: string,
  ) {
    await this.assertClinicTreatmentAccess(caseId, clinicOrganizationId, actorUserId);
    const comments = await this.fetchCommentsForClinicViewer(caseId);
    return {
      success: true,
      data: comments.map((c) => this.mapComment(c, LabCaseCommentSide.CLINIC)),
    };
  }

  async addForClinicTreatmentCase(
    caseId: string,
    clinicOrganizationId: string,
    actorUserId: string,
    dto: CreateLabCaseCommentDto,
  ) {
    await this.assertClinicTreatmentAccess(caseId, clinicOrganizationId, actorUserId);
    const created = await this.prisma.labCaseComment.create({
      data: {
        labCaseId: caseId,
        authorUserId: actorUserId,
        authorOrganizationId: clinicOrganizationId,
        authorSide: LabCaseCommentSide.CLINIC,
        body: dto.body.trim(),
        visibleToClinic: true,
      },
      include: commentInclude,
    });
    await this.labCaseActivity.record({
      labCaseId: caseId,
      type: LabCaseActivityType.CLINIC_COMMENT,
      actorUserId,
      payload: { commentId: created.id },
    });
    const labOrgId = await this.labOrgIdForCase(caseId);
    if (labOrgId) {
      void this.userNotifications.notify({
        organizationId: labOrgId,
        type: UserNotificationType.CLINIC_COMMENT,
        href: `/cases?caseId=${encodeURIComponent(caseId)}`,
        actorUserId,
        payload: { labCaseId: caseId, commentId: created.id },
        requiredPermission: 'TAB_CASES_READ',
      });
    }
    return { success: true, data: this.mapComment(created, LabCaseCommentSide.CLINIC) };
  }

  async countForCase(caseId: string) {
    const count = await this.prisma.labCaseComment.count({ where: { labCaseId: caseId } });
    return { success: true, data: { count } };
  }

  // ---------- Helpers ----------

  private fetchCommentsForClinicViewer(caseId: string) {
    return this.prisma.labCaseComment.findMany({
      where: {
        labCaseId: caseId,
        OR: [{ visibleToClinic: true }, { authorSide: LabCaseCommentSide.CLINIC }],
      },
      include: commentInclude,
      orderBy: { createdAt: 'asc' },
    });
  }

  private fetchComments(caseId: string, opts?: { visibleOnly?: boolean }) {
    return this.prisma.labCaseComment.findMany({
      where: {
        labCaseId: caseId,
        ...(opts?.visibleOnly ? { visibleToClinic: true } : {}),
      },
      include: commentInclude,
      orderBy: { createdAt: 'asc' },
    });
  }

  private mapComment(comment: CommentWithRelations, viewerSide: LabCaseCommentSide) {
    const showVisibilityStatus = viewerSide === LabCaseCommentSide.LAB;
    return {
      id: comment.id,
      body: comment.body,
      authorSide: comment.authorSide,
      authorName: comment.authorUser?.name ?? null,
      authorOrganizationName: comment.authorOrganization?.name ?? null,
      visibleToClinic: comment.visibleToClinic,
      createdAt: comment.createdAt.toISOString(),
      canToggleVisibility:
        viewerSide === LabCaseCommentSide.LAB &&
        comment.authorSide === LabCaseCommentSide.LAB,
      showVisibilityStatus,
    };
  }

  private async assertLabCanComment(
    caseId: string,
    labOrganizationId: string,
    actorUserId: string,
  ) {
    await this.assertLabCanViewCase(caseId, labOrganizationId, actorUserId);
    const membership = await this.getLabMembership(actorUserId, labOrganizationId);
    if (!hasEffectivePermission(membership, 'TAB_TASKS_EDIT')) {
      throw new AppException(ErrorCode.PERMISSION_ACCESS_TASKS, HttpStatus.FORBIDDEN);
    }
  }

  private async assertLabCanViewCase(
    caseId: string,
    labOrganizationId: string,
    actorUserId: string,
  ) {
    const labCase = await this.prisma.labCase.findFirst({
      where: {
        id: caseId,
        OR: [
          {
            sentAt: { not: null },
            sends: { some: { organizationId: labOrganizationId } },
          },
          {
            origin: LabCaseOrigin.LAB_INTERNAL,
            destinationOrganizationId: labOrganizationId,
          },
        ],
      },
      select: { id: true },
    });
    if (!labCase) {
      throw new AppException(ErrorCode.CASE_NOT_FOUND, HttpStatus.NOT_FOUND);
    }

    const membership = await this.getLabMembership(actorUserId, labOrganizationId);
    if (
      !hasEffectivePermission(membership, 'TAB_TASKS_READ') &&
      !hasEffectivePermission(membership, 'TAB_TASKS_EDIT')
    ) {
      throw new AppException(ErrorCode.PERMISSION_ACCESS_TASKS, HttpStatus.FORBIDDEN);
    }
  }

  private async getLabMembership(userId: string, organizationId: string) {
    const membership = await this.prisma.membership.findFirst({
      where: {
        userId,
        organizationId,
        OR: [{ isOwner: true }, { isActive: true }],
      },
      include: {
        permissions: { include: { permission: true } },
        organization: { include: { type: true, plan: true } },
      },
    });
    if (!membership) {
      throw new AppException(ErrorCode.PERMISSION_NOT_MEMBER, HttpStatus.FORBIDDEN);
    }
    return membership;
  }

  private async assertClinicOwnsCase(
    caseId: string,
    clinicOrganizationId: string,
    opts?: { requireSent?: boolean },
  ) {
    const labCase = await this.prisma.labCase.findFirst({
      where: {
        id: caseId,
        ...(opts?.requireSent ? { sentAt: { not: null } } : {}),
        treatment: { organizationId: clinicOrganizationId },
      },
      select: { id: true },
    });
    if (!labCase) {
      throw new AppException(ErrorCode.CASE_NOT_FOUND, HttpStatus.NOT_FOUND);
    }
  }

  private async assertClinicTreatmentAccess(
    caseId: string,
    clinicOrganizationId: string,
    actorUserId: string,
  ) {
    const labCase = await this.prisma.labCase.findFirst({
      where: {
        id: caseId,
        treatment: {
          organizationId: clinicOrganizationId,
          ...treatmentProviderScopeWhere(actorUserId),
        },
      },
      select: { id: true },
    });
    if (!labCase) {
      throw new AppException(ErrorCode.CASE_NOT_FOUND, HttpStatus.NOT_FOUND);
    }

    const membership = await this.prisma.membership.findFirst({
      where: {
        userId: actorUserId,
        organizationId: clinicOrganizationId,
        OR: [{ isOwner: true }, { isActive: true }],
      },
      include: {
        permissions: { include: { permission: true } },
        organization: { include: { type: true, plan: true } },
      },
    });
    if (!membership) {
      throw new AppException(ErrorCode.PERMISSION_NOT_MEMBER, HttpStatus.FORBIDDEN);
    }
    if (
      hasEffectivePermission(membership, 'TAB_TREATMENT_READ') ||
      hasEffectivePermission(membership, 'TAB_TREATMENT_EDIT')
    ) {
      return;
    }
    throw new AppException(ErrorCode.PERMISSION_ACCESS_TREATMENTS, HttpStatus.FORBIDDEN);
  }

  private async labOrgIdForCase(caseId: string): Promise<string | null> {
    const send = await this.prisma.labCaseSend.findFirst({
      where: { labCaseId: caseId },
      orderBy: { sentAt: 'asc' },
      select: { organizationId: true },
    });
    return send?.organizationId ?? null;
  }

  private async clinicOrgIdForCase(caseId: string): Promise<string | null> {
    const labCase = await this.prisma.labCase.findUnique({
      where: { id: caseId },
      select: {
        partnerClinicOrganizationId: true,
        treatment: { select: { organizationId: true } },
      },
    });
    return labCase?.treatment?.organizationId ?? labCase?.partnerClinicOrganizationId ?? null;
  }
}
