import { PrismaClient, EmployeeRole } from '@prisma/client'
import { clerkClient } from '@clerk/clerk-sdk-node'

const prisma = new PrismaClient()

// ─── Types ────────────────────────────────────────────────────

export interface InvitePayload {
  firstName: string
  lastName: string
  email: string
  role: EmployeeRole
}

export interface UpdateRolePayload {
  role: EmployeeRole
}

export interface TeamMember {
  id: string
  clerkUserId: string
  firstName: string
  lastName: string
  email: string
  phone: string | null
  role: EmployeeRole
  isActive: boolean
  createdAt: Date
  updatedAt: Date
  // hydrated from Clerk
  lastActiveAt?: Date | null
  invitationStatus?: 'accepted' | 'pending' | 'revoked'
}

// ─── List employees ───────────────────────────────────────────

export async function listEmployees(companyId: string): Promise<TeamMember[]> {
  const employees = await prisma.employee.findMany({
    where: { companyId },
    orderBy: [{ role: 'asc' }, { createdAt: 'asc' }],
  })

  // Hydrate last-active timestamps from Clerk in parallel
  const hydrated = await Promise.allSettled(
    employees.map(async (emp) => {
      try {
        const clerkUser = await clerkClient.users.getUser(emp.clerkUserId)
        return {
          ...emp,
          lastActiveAt: clerkUser.lastActiveAt
            ? new Date(clerkUser.lastActiveAt)
            : null,
          invitationStatus: 'accepted' as const,
        }
      } catch {
        // Clerk user not yet accepted invitation
        return {
          ...emp,
          lastActiveAt: null,
          invitationStatus: 'pending' as const,
        }
      }
    })
  )

  return hydrated.map((r) =>
    r.status === 'fulfilled' ? r.value : (r as any).value
  )
}

// ─── Get single employee (scoped to company) ──────────────────

export async function getEmployee(companyId: string, employeeId: string) {
  const employee = await prisma.employee.findFirst({
    where: { id: employeeId, companyId },
  })
  if (!employee) {
    throw Object.assign(new Error('Employee not found'), { statusCode: 404, code: 'employee_not_found' })
  }
  return employee
}

// ─── Invite ───────────────────────────────────────────────────
// 1. Send a Clerk invitation (creates the Clerk user + sends email)
// 2. Create the Employee row immediately so the company sees it in the list
//    with isActive=false until they accept and log in (Clerk webhook sets isActive=true)

export async function inviteEmployee(
  companyId: string,
  inviterId: string,
  payload: InvitePayload
) {
  const { firstName, lastName, email, role } = payload

  // Owners cannot be invited — must be the account creator
  if (role === 'OWNER') {
    throw Object.assign(
      new Error('Cannot invite a member with the OWNER role'),
      { statusCode: 400, code: 'invalid_role' }
    )
  }

  // Check for existing active employee with that email in this company
  const existing = await prisma.employee.findFirst({
    where: { companyId, email },
  })
  if (existing) {
    throw Object.assign(
      new Error('An employee with this email already exists in your team'),
      { statusCode: 409, code: 'employee_already_exists' }
    )
  }

  // Get the company's subdomain/name for the invitation email redirect URL
  const company = await prisma.company.findUniqueOrThrow({
    where: { id: companyId },
    include: { brand: { select: { subdomain: true, displayName: true } } },
  })

  // Send Clerk invitation
  // Clerk will create the user and email them a magic link
  let clerkInvitation: Awaited<ReturnType<typeof clerkClient.invitations.createInvitation>>

  try {
    clerkInvitation = await clerkClient.invitations.createInvitation({
      emailAddress: email,
      redirectUrl: `${process.env.DASHBOARD_URL}/onboarding/accept-invite`,
      publicMetadata: {
        companyId,
        companyName: company.brand?.displayName ?? company.name,
        role,
        invitedBy: inviterId,
      },
    })
  } catch (err: any) {
    // Clerk throws if email already has a Clerk account with a pending invite
    if (err?.errors?.[0]?.code === 'duplicate_record') {
      throw Object.assign(
        new Error('This email already has a pending invitation'),
        { statusCode: 409, code: 'duplicate_invitation' }
      )
    }
    throw err
  }

  // Create the Employee record with a placeholder clerkUserId
  // It will be updated when the user accepts the invite (via Clerk webhook)
  const employee = await prisma.employee.create({
    data: {
      companyId,
      clerkUserId: `pending_${clerkInvitation.id}`, // replaced by webhook
      firstName,
      lastName,
      email,
      role,
      isActive: false, // activated on invite acceptance
    },
  })

  return {
    employee,
    invitationId: clerkInvitation.id,
  }
}

// ─── Update role ──────────────────────────────────────────────

export async function updateEmployeeRole(
  companyId: string,
  requesterId: string,
  requesterRole: EmployeeRole,
  employeeId: string,
  payload: UpdateRolePayload
) {
  const target = await getEmployee(companyId, employeeId)

  // Only OWNER can change roles
  if (requesterRole !== 'OWNER') {
    throw Object.assign(
      new Error('Only the account owner can change team member roles'),
      { statusCode: 403, code: 'forbidden' }
    )
  }

  // Cannot change an OWNER's role (there must always be one owner)
  if (target.role === 'OWNER') {
    throw Object.assign(
      new Error('Cannot change the role of the account owner'),
      { statusCode: 400, code: 'cannot_change_owner_role' }
    )
  }

  // Cannot assign OWNER role via this endpoint
  if (payload.role === 'OWNER') {
    throw Object.assign(
      new Error('Cannot assign the OWNER role via this endpoint'),
      { statusCode: 400, code: 'invalid_role' }
    )
  }

  // Prevent self-role-change (edge case)
  if (target.id === requesterId) {
    throw Object.assign(
      new Error('You cannot change your own role'),
      { statusCode: 400, code: 'cannot_change_own_role' }
    )
  }

  const updated = await prisma.employee.update({
    where: { id: employeeId },
    data: { role: payload.role },
  })

  return updated
}

// ─── Deactivate ───────────────────────────────────────────────

export async function deactivateEmployee(
  companyId: string,
  requesterRole: EmployeeRole,
  employeeId: string
) {
  const target = await getEmployee(companyId, employeeId)

  if (requesterRole !== 'OWNER') {
    throw Object.assign(
      new Error('Only the account owner can deactivate team members'),
      { statusCode: 403, code: 'forbidden' }
    )
  }

  if (target.role === 'OWNER') {
    throw Object.assign(
      new Error('Cannot deactivate the account owner'),
      { statusCode: 400, code: 'cannot_deactivate_owner' }
    )
  }

  // Revoke Clerk session so they are immediately signed out
  if (!target.clerkUserId.startsWith('pending_')) {
    try {
      await clerkClient.users.banUser(target.clerkUserId)
    } catch {
      // Non-fatal — still deactivate in DB
    }
  }

  const updated = await prisma.employee.update({
    where: { id: employeeId },
    data: { isActive: false },
  })

  return updated
}

// ─── Reactivate ───────────────────────────────────────────────

export async function reactivateEmployee(
  companyId: string,
  requesterRole: EmployeeRole,
  employeeId: string
) {
  if (requesterRole !== 'OWNER') {
    throw Object.assign(
      new Error('Only the account owner can reactivate team members'),
      { statusCode: 403, code: 'forbidden' }
    )
  }

  const target = await getEmployee(companyId, employeeId)

  if (!target.clerkUserId.startsWith('pending_')) {
    try {
      await clerkClient.users.unbanUser(target.clerkUserId)
    } catch {
      // Non-fatal
    }
  }

  const updated = await prisma.employee.update({
    where: { id: employeeId },
    data: { isActive: true },
  })

  return updated
}

// ─── Remove (hard delete) ─────────────────────────────────────
// Only OWNER can remove. Removes employee row + revokes Clerk invite if pending.

export async function removeEmployee(
  companyId: string,
  requesterRole: EmployeeRole,
  employeeId: string
) {
  if (requesterRole !== 'OWNER') {
    throw Object.assign(
      new Error('Only the account owner can remove team members'),
      { statusCode: 403, code: 'forbidden' }
    )
  }

  const target = await getEmployee(companyId, employeeId)

  if (target.role === 'OWNER') {
    throw Object.assign(
      new Error('Cannot remove the account owner'),
      { statusCode: 400, code: 'cannot_remove_owner' }
    )
  }

  // Revoke pending Clerk invitation
  if (target.clerkUserId.startsWith('pending_')) {
    const inviteId = target.clerkUserId.replace('pending_', '')
    try {
      await clerkClient.invitations.revokeInvitation(inviteId)
    } catch {
      // Already accepted or expired — safe to ignore
    }
  } else {
    // Ban the Clerk user so they can't log in
    try {
      await clerkClient.users.banUser(target.clerkUserId)
    } catch {
      // Non-fatal
    }
  }

  await prisma.employee.delete({ where: { id: employeeId } })

  return { success: true }
}

// ─── Clerk webhook handler ────────────────────────────────────
// Called when a user accepts their invitation. Updates the Employee row
// with the real Clerk user ID and activates the account.

export async function handleInvitationAccepted(
  clerkUserId: string,
  email: string,
  companyId: string,
  role: EmployeeRole
) {
  const pendingEmployee = await prisma.employee.findFirst({
    where: {
      companyId,
      email,
      clerkUserId: { startsWith: 'pending_' },
    },
  })

  if (!pendingEmployee) return null

  const activated = await prisma.employee.update({
    where: { id: pendingEmployee.id },
    data: {
      clerkUserId,
      isActive: true,
      role, // in case it was updated between invite and acceptance
    },
  })

  return activated
}
