import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { generateDatabaseId } from '@documenso/lib/universal/id';
import { getHighestOrganisationRoleInGroup } from '@documenso/lib/utils/organisations';
import { prisma } from '@documenso/prisma';
import { OrganisationGroupType, OrganisationMemberRole } from '@prisma/client';

import { adminProcedure } from '../trpc';
import {
  ZUpdateOrganisationMemberRoleRequestSchema,
  ZUpdateOrganisationMemberRoleResponseSchema,
} from './update-organisation-member-role.types';

/**
 * Admin mutation to update organisation member role or transfer ownership.
 *
 * This mutation handles two scenarios:
 * 1. When role='OWNER': Transfers organisation ownership and promotes to ADMIN
 * 2. When role=ADMIN/MANAGER/MEMBER: Updates group membership
 *
 * Admin privileges bypass normal hierarchy restrictions.
 */
export const updateOrganisationMemberRoleRoute = adminProcedure
  .input(ZUpdateOrganisationMemberRoleRequestSchema)
  .output(ZUpdateOrganisationMemberRoleResponseSchema)
  .mutation(async ({ input, ctx }) => {
    const { organisationId, userId, role } = input;

    ctx.logger.info({
      input: {
        organisationId,
        userId,
        role,
      },
    });

    const organisation = await prisma.organisation.findUnique({
      where: {
        id: organisationId,
      },
      include: {
        groups: {
          where: {
            type: OrganisationGroupType.INTERNAL_ORGANISATION,
          },
        },
        members: {
          where: {
            userId,
          },
          include: {
            organisationGroupMembers: {
              include: {
                group: true,
              },
            },
          },
        },
      },
    });

    if (!organisation) {
      throw new AppError(AppErrorCode.NOT_FOUND, {
        message: 'Organisation not found',
      });
    }

    const [member] = organisation.members;

    if (!member) {
      throw new AppError(AppErrorCode.NOT_FOUND, {
        message: 'User is not a member of this organisation',
      });
    }

    const currentOrganisationRole = getHighestOrganisationRoleInGroup(
      member.organisationGroupMembers.flatMap((member) => member.group),
    );

    if (role === 'OWNER') {
      if (organisation.ownerUserId === userId) {
        throw new AppError(AppErrorCode.INVALID_REQUEST, {
          message: 'User is already the owner of this organisation',
        });
      }

      const currentMemberGroup = organisation.groups.find(
        (group) => group.organisationRole === currentOrganisationRole,
      );

      const adminGroup = organisation.groups.find((group) => group.organisationRole === OrganisationMemberRole.ADMIN);

      if (!currentMemberGroup) {
        ctx.logger.error({
          message: '[CRITICAL]: Missing internal group',
          organisationId,
          userId,
          role: currentOrganisationRole,
        });

        throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
          message: 'Current member group not found',
        });
      }

      if (!adminGroup) {
        ctx.logger.error({
          message: '[CRITICAL]: Missing internal group',
          organisationId,
          userId,
          targetRole: 'ADMIN',
        });

        throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
          message: 'Admin group not found',
        });
      }

      await prisma.$transaction(async (tx) => {
        await tx.organisation.update({
          where: {
            id: organisationId,
          },
          data: {
            ownerUserId: userId,
          },
        });

        if (currentOrganisationRole !== OrganisationMemberRole.ADMIN) {
          await tx.organisationGroupMember.delete({
            where: {
              organisationMemberId_groupId: {
                organisationMemberId: member.id,
                groupId: currentMemberGroup.id,
              },
            },
          });

          await tx.organisationGroupMember.create({
            data: {
              id: generateDatabaseId('group_member'),
              organisationMemberId: member.id,
              groupId: adminGroup.id,
            },
          });
        }
      });

      return;
    }

    const targetRole = role as OrganisationMemberRole;

    if (currentOrganisationRole === targetRole) {
      throw new AppError(AppErrorCode.INVALID_REQUEST, {
        message: 'User already has this role',
      });
    }

    if (userId === organisation.ownerUserId && targetRole !== OrganisationMemberRole.ADMIN) {
      throw new AppError(AppErrorCode.INVALID_REQUEST, {
        message: 'Organisation owner must be an admin. Transfer ownership first.',
      });
    }

    const currentMemberGroup = organisation.groups.find((group) => group.organisationRole === currentOrganisationRole);

    const newMemberGroup = organisation.groups.find((group) => group.organisationRole === targetRole);

    if (!currentMemberGroup) {
      ctx.logger.error({
        message: '[CRITICAL]: Missing internal group',
        organisationId,
        userId,
        role: currentOrganisationRole,
      });

      throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
        message: 'Current member group not found',
      });
    }

    if (!newMemberGroup) {
      ctx.logger.error({
        message: '[CRITICAL]: Missing internal group',
        organisationId,
        userId,
        targetRole,
      });

      throw new AppError(AppErrorCode.UNKNOWN_ERROR, {
        message: 'New member group not found',
      });
    }

    await prisma.$transaction(async (tx) => {
      await tx.organisationGroupMember.delete({
        where: {
          organisationMemberId_groupId: {
            organisationMemberId: member.id,
            groupId: currentMemberGroup.id,
          },
        },
      });

      await tx.organisationGroupMember.create({
        data: {
          id: generateDatabaseId('group_member'),
          organisationMemberId: member.id,
          groupId: newMemberGroup.id,
        },
      });
    });
  });
