import { v } from 'convex/values';

import { isRecord, getString } from '../../lib/utils/type-guards';
import { components } from '../_generated/api';
import { mutation } from '../_generated/server';
import * as AuditLogHelpers from '../audit_logs/helpers';
import { assertNotHeld } from '../governance/legal_hold_guard';
import { cascadeOnMemberRemoved } from '../lib/cascades/personalization_cascade';
import { getAuthUserIdentity } from '../lib/rls/auth/get_auth_user_identity';
import { isAdmin } from '../lib/rls/helpers/role_helpers';
import type {
  BetterAuthMember,
  BetterAuthUser,
  BetterAuthCreateResult,
  BetterAuthFindManyResult,
} from './types';
import { memberRoleValidator } from './validators';

function findOneMember(
  res: BetterAuthFindManyResult<BetterAuthMember> | undefined,
) {
  return res?.page?.[0];
}

function findOneUser(
  res: BetterAuthFindManyResult<BetterAuthUser> | undefined,
) {
  return res?.page?.[0];
}

function isBetterAuthCreateResult(
  value: unknown,
): value is BetterAuthCreateResult {
  return typeof value === 'object' && value !== null && '_id' in value;
}

export const addMember = mutation({
  args: {
    organizationId: v.string(),
    userId: v.string(),
    role: v.optional(memberRoleValidator),
  },
  returns: v.string(),
  handler: async (ctx, args) => {
    const authUser = await getAuthUserIdentity(ctx);
    if (!authUser) {
      throw new Error('Unauthenticated');
    }

    const callerMember = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [
          {
            field: 'organizationId',
            value: args.organizationId,
            operator: 'eq',
          },
          { field: 'userId', value: authUser.userId, operator: 'eq' },
        ],
      }),
    );
    if (!isAdmin(callerMember?.role)) {
      throw new Error('Only admins can add members');
    }

    const targetUser = findOneUser(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'user',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [{ field: '_id', value: args.userId, operator: 'eq' }],
      }),
    );

    const role = (args.role ?? 'member').toLowerCase();
    const created = await ctx.runMutation(
      components.betterAuth.adapter.create,
      {
        input: {
          model: 'member',
          data: {
            organizationId: args.organizationId,
            userId: args.userId,
            role,
            createdAt: Date.now(),
          },
        },
      },
    );

    const memberId = String(
      isBetterAuthCreateResult(created) ? created._id : created,
    );

    await AuditLogHelpers.logSuccess(ctx, {
      auditCtx: {
        organizationId: args.organizationId,
        actor: {
          id: authUser.userId,
          email: authUser.email,
          role: callerMember?.role,
          type: 'user',
        },
      },
      action: 'add_member',
      category: 'member',
      resourceType: 'member',
      resourceId: memberId,
      resourceName: targetUser?.email ?? targetUser?.name ?? args.userId,
      newState: { userId: args.userId, role },
    });

    return memberId;
  },
});

export const removeMember = mutation({
  args: {
    memberId: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const authUser = await getAuthUserIdentity(ctx);
    if (!authUser) {
      throw new Error('Unauthenticated');
    }

    const member = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [{ field: '_id', value: args.memberId, operator: 'eq' }],
      }),
    );
    if (!member?.organizationId) {
      throw new Error('Member not found');
    }

    const callerMember = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [
          {
            field: 'organizationId',
            value: member.organizationId,
            operator: 'eq',
          },
          { field: 'userId', value: authUser.userId, operator: 'eq' },
        ],
      }),
    );
    if (!isAdmin(callerMember?.role)) {
      throw new Error('Only admins can remove members');
    }

    if (member.role?.toLowerCase() === 'owner') {
      throw new Error('The organization owner cannot be removed');
    }

    const targetUser = member.userId
      ? findOneUser(
          await ctx.runQuery(components.betterAuth.adapter.findMany, {
            model: 'user',
            paginationOpts: { cursor: null, numItems: 1 },
            where: [{ field: '_id', value: member.userId, operator: 'eq' }],
          }),
        )
      : undefined;

    // Round-2 V4 P0-11: removing a member cascades into
    // `cascadeOnMemberRemoved`, which hard-deletes the user's userMemories
    // + userPreferences + (per Commit 1) thread-bound chat-uploads scoped
    // to this org. Refuse if the org is on a hold OR if the member's
    // userId is on a userMembership custodian hold — without this gate,
    // an admin could silently wipe a held custodian's footprint.
    if (member.userId) {
      await assertNotHeld(
        ctx,
        member.organizationId,
        'userMembership',
        member.userId,
        undefined,
        member.userId,
      );
    }

    await ctx.runMutation(components.betterAuth.adapter.deleteOne, {
      input: {
        model: 'member',
        where: [{ field: '_id', value: args.memberId, operator: 'eq' }],
      },
    });

    if (member.userId) {
      await cascadeOnMemberRemoved(ctx, member.userId, member.organizationId);
    }

    await AuditLogHelpers.logSuccess(ctx, {
      auditCtx: {
        organizationId: member.organizationId,
        actor: {
          id: authUser.userId,
          email: authUser.email,
          role: callerMember?.role,
          type: 'user',
        },
      },
      action: 'remove_member',
      category: 'member',
      resourceType: 'member',
      resourceId: args.memberId,
      resourceName: targetUser?.email ?? member.userId,
      previousState: { userId: member.userId, role: member.role },
    });

    return null;
  },
});

export const updateMemberRole = mutation({
  args: {
    memberId: v.string(),
    role: memberRoleValidator,
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const authUser = await getAuthUserIdentity(ctx);
    if (!authUser) {
      throw new Error('Unauthenticated');
    }

    const member = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [{ field: '_id', value: args.memberId, operator: 'eq' }],
      }),
    );
    if (!member?.organizationId) {
      throw new Error('Member not found');
    }

    const callerMember = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [
          {
            field: 'organizationId',
            value: member.organizationId,
            operator: 'eq',
          },
          { field: 'userId', value: authUser.userId, operator: 'eq' },
        ],
      }),
    );
    if (!isAdmin(callerMember?.role)) {
      throw new Error('Only admins can update member roles');
    }

    if (member.role?.toLowerCase() === 'owner') {
      throw new Error('The organization owner role cannot be changed');
    }

    if (args.role.toLowerCase() === 'owner') {
      throw new Error('The owner role cannot be assigned manually');
    }

    const orgResult = await ctx.runQuery(
      components.betterAuth.adapter.findMany,
      {
        model: 'organization',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [
          {
            field: '_id',
            value: member.organizationId,
            operator: 'eq',
          },
        ],
      },
    );
    const orgRaw = orgResult?.page?.[0];
    if (isRecord(orgRaw)) {
      const rawMetadata = getString(orgRaw, 'metadata');
      if (rawMetadata) {
        try {
          const parsed: unknown = JSON.parse(rawMetadata);
          if (isRecord(parsed) && parsed.creatorId === member.userId) {
            throw new Error('The organization creator role cannot be changed');
          }
        } catch (e) {
          if (e instanceof Error && e.message.includes('creator')) throw e;
        }
      }
    }

    const targetUser = member.userId
      ? findOneUser(
          await ctx.runQuery(components.betterAuth.adapter.findMany, {
            model: 'user',
            paginationOpts: { cursor: null, numItems: 1 },
            where: [{ field: '_id', value: member.userId, operator: 'eq' }],
          }),
        )
      : undefined;

    const previousRole = member.role;
    const newRole = args.role.toLowerCase();

    if (isAdmin(previousRole) && !isAdmin(newRole)) {
      const adminMembers = await ctx.runQuery(
        components.betterAuth.adapter.findMany,
        {
          model: 'member',
          paginationOpts: { cursor: null, numItems: 100 },
          where: [
            {
              field: 'organizationId',
              value: member.organizationId,
              operator: 'eq',
            },
          ],
        },
      );
      const adminCount = (adminMembers?.page ?? []).filter(
        (m: { role?: string }) => isAdmin(m.role),
      ).length;
      if (adminCount <= 1) {
        throw new Error(
          'Cannot demote the last admin. The organization must have at least one admin or owner.',
        );
      }
    }

    await ctx.runMutation(components.betterAuth.adapter.updateMany, {
      input: {
        model: 'member',
        where: [{ field: '_id', value: args.memberId, operator: 'eq' }],
        update: { role: newRole },
      },
      paginationOpts: { cursor: null, numItems: 1 },
    });

    await AuditLogHelpers.logSuccess(ctx, {
      auditCtx: {
        organizationId: member.organizationId,
        actor: {
          id: authUser.userId,
          email: authUser.email,
          role: callerMember?.role,
          type: 'user',
        },
      },
      action: 'update_member_role',
      category: 'member',
      resourceType: 'member',
      resourceId: args.memberId,
      resourceName: targetUser?.email ?? member.userId,
      previousState: { role: previousRole },
      newState: { role: newRole },
    });

    return null;
  },
});

export const transferOwnership = mutation({
  args: {
    targetMemberId: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const authUser = await getAuthUserIdentity(ctx);
    if (!authUser) {
      throw new Error('Unauthenticated');
    }

    const targetMember = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [{ field: '_id', value: args.targetMemberId, operator: 'eq' }],
      }),
    );
    if (!targetMember?.organizationId) {
      throw new Error('Member not found');
    }

    const callerMember = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [
          {
            field: 'organizationId',
            value: targetMember.organizationId,
            operator: 'eq',
          },
          { field: 'userId', value: authUser.userId, operator: 'eq' },
        ],
      }),
    );
    if (callerMember?.role?.toLowerCase() !== 'owner') {
      throw new Error('Only the organization owner can transfer ownership');
    }

    if (targetMember.role?.toLowerCase() === 'owner') {
      throw new Error('Target member is already the owner');
    }

    // Promote target to owner
    await ctx.runMutation(components.betterAuth.adapter.updateMany, {
      input: {
        model: 'member',
        where: [{ field: '_id', value: args.targetMemberId, operator: 'eq' }],
        update: { role: 'owner' },
      },
      paginationOpts: { cursor: null, numItems: 1 },
    });

    // Demote caller to admin
    await ctx.runMutation(components.betterAuth.adapter.updateMany, {
      input: {
        model: 'member',
        where: [{ field: '_id', value: callerMember._id, operator: 'eq' }],
        update: { role: 'admin' },
      },
      paginationOpts: { cursor: null, numItems: 1 },
    });

    const targetUser = targetMember.userId
      ? findOneUser(
          await ctx.runQuery(components.betterAuth.adapter.findMany, {
            model: 'user',
            paginationOpts: { cursor: null, numItems: 1 },
            where: [
              { field: '_id', value: targetMember.userId, operator: 'eq' },
            ],
          }),
        )
      : undefined;

    await AuditLogHelpers.logSuccess(ctx, {
      auditCtx: {
        organizationId: targetMember.organizationId,
        actor: {
          id: authUser.userId,
          email: authUser.email,
          role: 'owner',
          type: 'user',
        },
      },
      action: 'transfer_ownership',
      category: 'member',
      resourceType: 'member',
      resourceId: args.targetMemberId,
      resourceName: targetUser?.email ?? targetMember.userId,
      previousState: { previousOwner: authUser.userId },
      newState: { newOwner: targetMember.userId },
    });

    return null;
  },
});

export const updateMemberDisplayName = mutation({
  args: {
    memberId: v.string(),
    displayName: v.string(),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    const authUser = await getAuthUserIdentity(ctx);
    if (!authUser) {
      throw new Error('Unauthenticated');
    }

    const member = findOneMember(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'member',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [{ field: '_id', value: args.memberId, operator: 'eq' }],
      }),
    );
    if (!member?.userId || !member.organizationId) {
      throw new Error('Member not found');
    }

    const targetUser = findOneUser(
      await ctx.runQuery(components.betterAuth.adapter.findMany, {
        model: 'user',
        paginationOpts: { cursor: null, numItems: 1 },
        where: [{ field: '_id', value: member.userId, operator: 'eq' }],
      }),
    );
    const previousName = targetUser?.name;

    let callerRole: string | undefined;
    const isOwnProfile = authUser.userId === member.userId;
    if (!isOwnProfile) {
      const callerMember = findOneMember(
        await ctx.runQuery(components.betterAuth.adapter.findMany, {
          model: 'member',
          paginationOpts: { cursor: null, numItems: 1 },
          where: [
            {
              field: 'organizationId',
              value: member.organizationId,
              operator: 'eq',
            },
            { field: 'userId', value: authUser.userId, operator: 'eq' },
          ],
        }),
      );
      callerRole = callerMember?.role;
      if (!isAdmin(callerMember?.role)) {
        throw new Error('Only admins can update other members names');
      }
    }

    await ctx.runMutation(components.betterAuth.adapter.updateMany, {
      input: {
        model: 'user',
        where: [{ field: '_id', value: member.userId, operator: 'eq' }],
        update: { name: args.displayName },
      },
      paginationOpts: { cursor: null, numItems: 1 },
    });

    await AuditLogHelpers.logSuccess(ctx, {
      auditCtx: {
        organizationId: member.organizationId,
        actor: {
          id: authUser.userId,
          email: authUser.email,
          role: callerRole,
          type: 'user',
        },
      },
      action: 'update_member_name',
      category: 'member',
      resourceType: 'member',
      resourceId: args.memberId,
      resourceName: targetUser?.email ?? member.userId,
      previousState: { name: previousName },
      newState: { name: args.displayName },
    });

    return null;
  },
});
