import { z } from 'zod'
import { router, memberProcedure, adminProcedure } from '../trpc'
import { sendInviteEmail, sendAdminCredentialsEmail } from '@/lib/email'
import crypto from 'node:crypto'
import { prisma } from '@innungsapp/shared'
// @ts-ignore — Better Auth exposes its password utilities via `better-auth/crypto`
import { hashPassword } from 'better-auth/crypto'

/**
 * Creates a credential (email+password) account for a user that has no such account yet.
 * Better Auth stores passwords in the `account` table with providerId='credential'.
 * Uses Better Auth's own `hashPassword` to ensure the hash format matches its verifyPassword.
 */
async function createCredentialAccount(userId: string, password: string): Promise<void> {
  const hashed = await hashPassword(password)
  await prisma.account.create({
    data: {
      id: crypto.randomUUID(),
      accountId: userId,
      providerId: 'credential',
      userId,
      password: hashed,
    },
  })
}

/**
 * Creates a user + credential account directly in the DB, bypassing Better Auth's admin API.
 * auth.api.createUser requires the calling session to have role='admin' in Better Auth's own
 * user table, which our custom role system doesn't set. This avoids the 403 FORBIDDEN error.
 */
async function createUserDirectly(opts: { email: string; name: string; password: string; mustChangePassword?: boolean }) {
  const userId = crypto.randomUUID()
  await prisma.user.create({
    data: {
      id: userId,
      name: opts.name,
      email: opts.email,
      emailVerified: false,
      mustChangePassword: opts.mustChangePassword ?? false,
    },
  })
  await createCredentialAccount(userId, opts.password)
  return { id: userId }
}

const nonEmptyString = (min = 2) =>
  z.preprocess((val) => (val === '' ? undefined : val), z.string().min(min).optional())

const MemberInput = z.object({
  name: z.string().min(2),
  betrieb: z.preprocess((v) => (v === '' ? undefined : v), z.string().optional()),
  // Member.sparte is required in Prisma; map "not selected" to a safe default.
  sparte: z.preprocess((v) => (v === '' ? undefined : v), z.string().optional().default('Sonstiges')),
  ort: z.preprocess((v) => (v === '' ? undefined : v), z.string().optional()),
  telefon: z.string().optional(),
  email: z.string().email(),
  status: z.enum(['aktiv', 'ruhend', 'ausgetreten']).default('aktiv'),
  istAusbildungsbetrieb: z.boolean().default(false),
  seit: z.number().int().min(1900).max(2100).optional(),
  role: z.enum(['member', 'admin']).optional().default('member'),
  password: z.preprocess((val) => (val === '' ? undefined : val), z.string().min(8).optional()),
})

export const membersRouter = router({
  /**
   * List all members in the user's org
   */
  list: memberProcedure
    .input(
      z.object({
        search: z.string().optional(),
        status: z.enum(['aktiv', 'ruhend', 'ausgetreten']).optional(),
        ausbildungsbetrieb: z.boolean().optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      const members = await ctx.prisma.member.findMany({
        where: {
          orgId: ctx.orgId,
          ...(input.status && { status: input.status }),
          ...(input.ausbildungsbetrieb !== undefined && {
            istAusbildungsbetrieb: input.ausbildungsbetrieb,
          }),
          ...(input.search && {
            OR: [
              { name: { contains: input.search } },
              { betrieb: { contains: input.search } },
              { ort: { contains: input.search } },
              { sparte: { contains: input.search } },
            ],
          }),
        },
        orderBy: { name: 'asc' },
      })
      return members
    }),

  byId: memberProcedure
    .input(z.object({ id: z.string() }))
    .query(async ({ ctx, input }) => {
      let member = await ctx.prisma.member.findFirst({
        where: { id: input.id, orgId: ctx.orgId },
      })

      let role = 'member'
      if (member?.userId) {
        const ur = await ctx.prisma.userRole.findUnique({ where: { orgId_userId: { orgId: ctx.orgId, userId: member.userId } } })
        if (ur && ur.role === 'admin') role = 'admin'
      }

      if (!member) {
        // Try finding the member by userId (list page uses userId as ID for admins)
        const memberByUserId = await ctx.prisma.member.findFirst({
          where: { userId: input.id, orgId: ctx.orgId },
        })
        if (memberByUserId) {
          const ur2 = await ctx.prisma.userRole.findUnique({
            where: { orgId_userId: { orgId: ctx.orgId, userId: input.id } },
          })
          if (ur2?.role === 'admin') role = 'admin'
          return { ...memberByUserId, role }
        }

        // Fallback: Check if the ID belongs to a user who is an admin in this org
        const adminRole = await ctx.prisma.userRole.findUnique({
          where: { orgId_userId: { orgId: ctx.orgId, userId: input.id } },
          include: { user: true }
        })

        if (!adminRole) {
          // Last resort A: find member by ID regardless of org (org mismatch scenario)
          const memberAnyOrg = await ctx.prisma.member.findUnique({ where: { id: input.id } })
          if (memberAnyOrg) {
            const callerHasAccess = await ctx.prisma.userRole.findFirst({
              where: { userId: ctx.session.user.id, orgId: memberAnyOrg.orgId, role: 'admin' }
            })
            if (callerHasAccess) {
              const ur = memberAnyOrg.userId
                ? await ctx.prisma.userRole.findUnique({
                  where: { orgId_userId: { orgId: memberAnyOrg.orgId, userId: memberAnyOrg.userId } }
                })
                : null
              return { ...memberAnyOrg, role: ur?.role ?? 'member' }
            }
          }

          // Last resort B: input.id is a userId whose UserRole is in a different org than ctx.orgId
          const roleAnyOrg = await ctx.prisma.userRole.findFirst({
            where: { userId: input.id },
            include: { user: true },
          })
          if (roleAnyOrg) {
            const callerHasAccess = await ctx.prisma.userRole.findFirst({
              where: { userId: ctx.session.user.id, orgId: roleAnyOrg.orgId, role: 'admin' }
            })
            if (callerHasAccess) {
              const memberRecord = await ctx.prisma.member.findFirst({
                where: { userId: input.id, orgId: roleAnyOrg.orgId }
              })
              if (memberRecord) return { ...memberRecord, role: roleAnyOrg.role }
              // Admin without member record — return mock
              return {
                id: roleAnyOrg.userId,
                orgId: roleAnyOrg.orgId,
                userId: roleAnyOrg.userId,
                name: roleAnyOrg.user.name,
                betrieb: 'Administrator',
                sparte: 'Sonderfunktion',
                ort: '',
                telefon: '',
                email: roleAnyOrg.user.email,
                status: 'aktiv',
                istAusbildungsbetrieb: false,
                seit: new Date().getFullYear(),
                avatarUrl: null,
                pushToken: null,
                createdAt: roleAnyOrg.createdAt,
                updatedAt: roleAnyOrg.createdAt,
                role: roleAnyOrg.role,
              } as any
            }
          }

          throw new Error('Member not found')
        }
        if (adminRole.role !== 'admin') throw new Error('Member not found')

        // Mock a Member object so the frontend form doesn't crash
        member = {
          id: adminRole.userId, // use userId here to update
          orgId: ctx.orgId,
          userId: adminRole.userId,
          name: adminRole.user.name,
          betrieb: 'Administrator',
          sparte: 'Sonderfunktion',
          ort: '',
          telefon: '',
          email: adminRole.user.email,
          status: 'aktiv',
          istAusbildungsbetrieb: false,
          seit: new Date().getFullYear(),
          avatarUrl: null,
          pushToken: null,
          createdAt: adminRole.createdAt,
          updatedAt: adminRole.createdAt,
        } as any
        role = 'admin'
      }

      return { ...member, role }
    }),

  create: adminProcedure.input(MemberInput).mutation(async ({ ctx, input }) => {
    const { role, password, ...rest } = input

    // 1. Create the member record
    const member = await ctx.prisma.member.create({
      data: {
        ...rest,
        sparte: rest.sparte || 'Sonstiges',
        orgId: ctx.orgId,
      } as any,
    })

    // 2. Create a User account if a password was provided OR role is 'admin',
    //    so the role is always persisted (no email sent here).
    if (password || role === 'admin') {
      try {
        const existing = await ctx.prisma.user.findUnique({ where: { email: input.email } })
        let userId: string | undefined = existing?.id
        const effectivePassword = password || crypto.randomBytes(8).toString('hex')

        if (!userId) {
          // Create user + credential account directly via Prisma.
          // auth.api.createUser requires the caller to have role='admin' in Better Auth's own
          // user table (not our custom UserRole table), which causes a 403 FORBIDDEN.
          const newUserRecord = await createUserDirectly({
            name: input.name,
            email: input.email,
            password: effectivePassword,
            mustChangePassword: true,
          })
          userId = newUserRecord.id
        } else if (password) {
          // User exists and a password was explicitly set.
          // Check if they already have a credential account; if not, create one directly.
          const credAccount = await ctx.prisma.account.findFirst({
            where: { userId, providerId: 'credential' }
          })
          if (credAccount) {
            // Credential account exists — update the password hash directly
            const newHash = await hashPassword(password)
            await ctx.prisma.account.update({ where: { id: credAccount.id }, data: { password: newHash } })
          } else {
            // No credential account yet — create one directly in the DB
            await createCredentialAccount(userId, password)
          }
        }

        if (userId) {
          await ctx.prisma.member.update({ where: { id: member.id }, data: { userId } })
          await ctx.prisma.userRole.upsert({
            where: { orgId_userId: { orgId: ctx.orgId, userId } },
            create: { orgId: ctx.orgId, userId, role: role ?? 'member' },
            update: { role: role ?? 'member' },
          })
        }
      } catch (e) {
        console.error('Failed to create user account during member creation', e)
      }
    }

    return member
  }),


  /**
   * Create member + send invite email (admin only)
   */
  invite: adminProcedure
    .input(MemberInput)
    .mutation(async ({ ctx, input }) => {
      const { role, password, ...memberData } = input

      // 1. Create member record
      const member = await ctx.prisma.member.create({
        data: {
          ...memberData,
          sparte: memberData.sparte || 'Sonstiges',
          orgId: ctx.orgId,
        } as any,
      })

      const org = await ctx.prisma.organization.findUniqueOrThrow({
        where: { id: ctx.orgId },
      })

      // 2. Create/get User directly via Prisma to avoid auth.api.createUser 403 FORBIDDEN.
      // (Better Auth's admin API requires the caller's session user to have role='admin' in
      //  its own user table, which our custom UserRole system doesn't set.)
      let targetUserId: string | undefined
      try {
        const effectivePassword = password || (role === 'admin' ? crypto.randomBytes(6).toString('hex') : undefined)

        if (effectivePassword) {
          const newUserId = (await createUserDirectly({
            name: input.name,
            email: input.email,
            password: effectivePassword,
            mustChangePassword: !password && role === 'admin',
          })).id
          targetUserId = newUserId
        }

        if (targetUserId) {
          // link user to member
          await ctx.prisma.member.update({
            where: { id: member.id },
            data: { userId: targetUserId }
          })

          // if admin, set role
          if (role === 'admin') {
            await ctx.prisma.userRole.upsert({
              where: { orgId_userId: { orgId: ctx.orgId, userId: targetUserId } },
              create: { orgId: ctx.orgId, userId: targetUserId, role: 'admin' },
              update: { role: 'admin' }
            })

            // Send admin credentials
            await sendAdminCredentialsEmail({
              to: input.email,
              adminName: input.name,
              orgName: org.name,
              password: password!,
              loginUrl: process.env.BETTER_AUTH_URL!
            })
          }
        }
      } catch (e) {
        // User may already exist
        const existingUser = await ctx.prisma.user.findUnique({ where: { email: input.email } })
        if (existingUser) {
          targetUserId = existingUser.id
          await ctx.prisma.member.update({
            where: { id: member.id },
            data: { userId: targetUserId }
          })

          if (role === 'admin') {
            await ctx.prisma.userRole.upsert({
              where: { orgId_userId: { orgId: ctx.orgId, userId: targetUserId } },
              create: { orgId: ctx.orgId, userId: targetUserId, role: 'admin' },
              update: { role: 'admin' }
            })
          }
        }
      }

      // 3. Send magic link for members (if not admin or if admin creation failed to send credentials)
      if (role === 'member') {
        await sendInviteEmail({
          to: input.email,
          memberName: input.name,
          orgName: org.name,
          apiUrl: process.env.BETTER_AUTH_URL!,
        })
      }

      return member
    }),

  /**
   * Update member (admin only)
   */
  update: adminProcedure
    .input(z.object({
      id: z.string(),
      data: z.object({
        name: nonEmptyString(),
        betrieb: nonEmptyString(),
        sparte: nonEmptyString(),
        ort: nonEmptyString(),
        telefon: z.string().optional(),
        email: z.preprocess((v) => (v === '' ? undefined : v), z.string().email().optional()),
        status: z.enum(['aktiv', 'ruhend', 'ausgetreten']).optional(),
        istAusbildungsbetrieb: z.boolean().optional(),
        seit: z.number().int().min(1900).max(2100).optional(),
        role: z.enum(['member', 'admin']).optional(),
        password: z.preprocess((val) => (val === '' ? undefined : val), z.string().min(8).optional()),
      }),
    }))
    .mutation(async ({ ctx, input }) => {
      const { role, password, ...memberData } = input.data

      let member = await ctx.prisma.member.findFirst({
        where: { id: input.id, orgId: ctx.orgId },
      })

      // If not found by member ID, try by userId (list page links use userId for admins)
      if (!member) {
        member = await ctx.prisma.member.findFirst({
          where: { userId: input.id, orgId: ctx.orgId },
        })
      }

      // For existing members, targetUserId is their associated user ID (can be null).
      // For fallback admins (no member record), input.id is the User ID.
      let targetUserId = member ? member.userId : input.id

      // If they don't have a User record yet, but we want to update their role to Admin,
      // we need to pre-create their User record so we can attach the UserRole.
      if (member && !targetUserId && role === 'admin') {
        const email = memberData.email || member.email
        const name = memberData.name || member.name
        // Always generate a password – without one Better Auth creates no credential account
        // and the user can never log in with email/password.
        const effectivePassword = password || crypto.randomBytes(8).toString('hex')
        try {
          const user = await ctx.prisma.user.findUnique({ where: { email } })
          if (user) {
            targetUserId = user.id
            // If the existing user has no credential account (e.g. OAuth-only), set a password now
            const credAccount = await ctx.prisma.account.findFirst({
              where: { userId: user.id, providerId: 'credential' }
            })
            if (!credAccount) {
              // No credential account — create one directly (updateUser can't create from scratch)
              await createCredentialAccount(user.id, effectivePassword)
            }
          } else {
            const newUserId = (await createUserDirectly({
              email,
              name,
              password: effectivePassword,
              mustChangePassword: !password,
            })).id
            targetUserId = newUserId
          }
          if (targetUserId) {
            await ctx.prisma.member.update({
              where: { id: member.id },
              data: { userId: targetUserId }
            })
          }
        } catch (e) {
          console.error("Failed to pre-create user for pending invitee admin upgrade", e)
        }
      }

      if (member) {
        if (Object.keys(memberData).length > 0) {
          await ctx.prisma.member.update({
            where: { id: member.id },
            data: memberData as any,
          })
        }
      } else {
        // Fallback: Creating skeleton member for former pure-admin that is now turning to member
        const existingAdmin = await ctx.prisma.userRole.findUnique({
          where: { orgId_userId: { orgId: ctx.orgId, userId: targetUserId as string } }
        })
        if (!existingAdmin || existingAdmin.role !== 'admin') throw new Error('Member not found')

        // Since we are creating a member profile, ensure required fields
        const createData = {
          betrieb: memberData.betrieb || 'Administrator',
          sparte: memberData.sparte || 'Sonderfunktion',
          ort: memberData.ort || '',
          telefon: memberData.telefon || '',
          email: memberData.email || 'no-reply@innungsapp.de',
          status: memberData.status || 'aktiv',
          name: memberData.name || 'Unbekannt',
        }

        const existingMemberByUserId = await ctx.prisma.member.findFirst({
          where: { userId: targetUserId as string, orgId: ctx.orgId }
        })

        if (existingMemberByUserId) {
          // Member record already exists — just update it with any new data
          await ctx.prisma.member.update({
            where: { id: existingMemberByUserId.id },
            data: createData as any,
          })
        } else {
          await ctx.prisma.member.create({
            data: {
              ...createData,
              orgId: ctx.orgId,
              userId: targetUserId
            }
          })
        }
      }

      if (role && targetUserId) {
        // Update the role in UserRole
        await ctx.prisma.userRole.upsert({
          where: { orgId_userId: { orgId: ctx.orgId, userId: targetUserId } },
          create: { orgId: ctx.orgId, userId: targetUserId, role },
          update: { role }
        })
      }

      // When promoting to admin, ensure a credential (email+password) account exists
      // so the user can log in to the admin dashboard.
      if (role === 'admin' && targetUserId && !password) {
        const credAccount = await ctx.prisma.account.findFirst({
          where: { userId: targetUserId, providerId: 'credential' },
        })
        if (!credAccount) {
          const generatedPassword = crypto.randomBytes(8).toString('hex')
          await createCredentialAccount(targetUserId, generatedPassword)
          try {
            const targetUser = await ctx.prisma.user.findUnique({ where: { id: targetUserId } })
            const org = await ctx.prisma.organization.findUnique({ where: { id: ctx.orgId } })
            if (targetUser && org) {
              await sendAdminCredentialsEmail({
                to: targetUser.email,
                adminName: targetUser.name,
                orgName: org.name,
                password: generatedPassword,
                loginUrl: process.env.BETTER_AUTH_URL!,
              })
            }
          } catch (e) {
            console.error('Failed to send admin credentials email after auto-credential creation', e)
          }
        }
      }

      // Update password if provided
      if (password && targetUserId) {
        // Check if the user already has a credential account.
        // auth.api.updateUser can update an existing credential account password, but CANNOT create one.
        const existingCredAccount = await ctx.prisma.account.findFirst({
          where: { userId: targetUserId, providerId: 'credential' }
        })
        if (existingCredAccount) {
          // Update password hash directly — auth.api.updateUser requires Better Auth admin role
          // which our custom UserRole system doesn't set, causing silent failures.
          const newHash = await hashPassword(password)
          await ctx.prisma.account.update({
            where: { id: existingCredAccount.id },
            data: { password: newHash },
          })
        } else {
          // No credential account — create one from scratch using Better Auth's own hash format
          await createCredentialAccount(targetUserId, password)
        }
        // Admin has set a password → user must change it on next login
        await ctx.prisma.user.update({
          where: { id: targetUserId },
          data: { mustChangePassword: true },
        })
      }

      // Keep user.name in sync when member name changes
      if (memberData.name && targetUserId) {
        const user = await ctx.prisma.user.findUnique({ where: { id: targetUserId } })
        if (user) {
          await ctx.prisma.user.update({ where: { id: targetUserId }, data: { name: memberData.name } })
        }
      }
      return member || { success: true }
    }),

  /**
   * Send/resend invite to existing member (admin only)
   */
  resendInvite: adminProcedure
    .input(z.object({ memberId: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const member = await ctx.prisma.member.findFirstOrThrow({
        where: { id: input.memberId, orgId: ctx.orgId },
      })
      const org = await ctx.prisma.organization.findUniqueOrThrow({
        where: { id: ctx.orgId },
      })
      await sendInviteEmail({
        to: member.email,
        memberName: member.name,
        orgName: org.name,
        apiUrl: process.env.BETTER_AUTH_URL!,
      })
      return { success: true }
    }),

  /**
   * Get own member profile
   */
  me: memberProcedure.query(async ({ ctx }) => {
    const member = await ctx.prisma.member.findFirst({
      where: { userId: ctx.session.user.id, orgId: ctx.orgId },
      include: { org: true },
    })
    return member
  }),

  /**
   * Update own member profile
   */
  updateMe: memberProcedure
    .input(
      z.object({
        name: z.string().min(2).optional(),
        email: z.string().email().optional(),
        telefon: z.string().optional(),
        ort: z.string().min(2).optional(),
        betrieb: z.string().min(2).optional(),
        sparte: z.string().min(2).optional(),
        istAusbildungsbetrieb: z.boolean().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const member = await ctx.prisma.member.findFirst({
        where: { userId: ctx.session.user.id, orgId: ctx.orgId },
      })
      if (!member) throw new Error('Member not found')

      const updated = await ctx.prisma.member.update({
        where: { id: member.id },
        data: input,
      })

      // Keep user.name in sync when member name changes
      if (input.name) {
        await ctx.prisma.user.update({
          where: { id: ctx.session.user.id },
          data: { name: input.name },
        })
      }
      return updated
    }),

  /**
   * Delete member (admin only)
   */
  delete: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const member = await ctx.prisma.member.findFirst({
        where: { id: input.id, orgId: ctx.orgId },
      })

      if (!member) {
        // Fallback for user-based "pure" admins
        const adminRole = await ctx.prisma.userRole.findUnique({
          where: { orgId_userId: { orgId: ctx.orgId, userId: input.id } }
        })
        if (!adminRole) throw new Error('Member not found')

        if (adminRole.userId === ctx.session.user.id) {
          throw new Error('Sie können Ihren eigenen Account nicht löschen.')
        }

        await ctx.prisma.userRole.delete({ where: { id: adminRole.id } })
        return { success: true }
      }

      if (member.userId === ctx.session.user.id) {
        throw new Error('Sie können Ihren eigenen Account nicht löschen.')
      }

      // 1. Remove UserRole link if exists
      if (member.userId) {
        await ctx.prisma.userRole.deleteMany({
          where: { orgId: ctx.orgId, userId: member.userId }
        })
      }

      // 2. Delete member profile
      await ctx.prisma.member.delete({
        where: { id: member.id }
      })

      return { success: true }
    }),
})
