import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
import { db } from '@sim/db'
import { credentialSet, credentialSetInvitation, member, organization, user } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { generateId } from '@sim/utils/id'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getEmailSubject, renderPollingGroupInvitationEmail } from '@/components/emails'
import {
  cancelCredentialSetInvitationQuerySchema,
  createCredentialSetInvitationContract,
} from '@/lib/api/contracts/credential-sets'
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import { sendEmail } from '@/lib/messaging/email/mailer'

const logger = createLogger('CredentialSetInvite')

async function getCredentialSetWithAccess(credentialSetId: string, userId: string) {
  const [set] = await db
    .select({
      id: credentialSet.id,
      organizationId: credentialSet.organizationId,
      name: credentialSet.name,
      providerId: credentialSet.providerId,
    })
    .from(credentialSet)
    .where(eq(credentialSet.id, credentialSetId))
    .limit(1)

  if (!set) return null

  const [membership] = await db
    .select({ role: member.role })
    .from(member)
    .where(and(eq(member.userId, userId), eq(member.organizationId, set.organizationId)))
    .limit(1)

  if (!membership) return null

  return { set, role: membership.role }
}

export const GET = withRouteHandler(
  async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => {
    const session = await getSession()

    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    // Check plan access (team/enterprise) or env var override
    const hasAccess = await hasCredentialSetsAccess(session.user.id)
    if (!hasAccess) {
      return NextResponse.json(
        { error: 'Credential sets require a Team or Enterprise plan' },
        { status: 403 }
      )
    }

    const { id } = await params
    const result = await getCredentialSetWithAccess(id, session.user.id)

    if (!result) {
      return NextResponse.json({ error: 'Credential set not found' }, { status: 404 })
    }

    const invitations = await db
      .select()
      .from(credentialSetInvitation)
      .where(eq(credentialSetInvitation.credentialSetId, id))

    return NextResponse.json({ invitations })
  }
)

export const POST = withRouteHandler(
  async (req: NextRequest, context: { params: Promise<{ id: string }> }) => {
    const session = await getSession()

    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    // Check plan access (team/enterprise) or env var override
    const hasAccess = await hasCredentialSetsAccess(session.user.id)
    if (!hasAccess) {
      return NextResponse.json(
        { error: 'Credential sets require a Team or Enterprise plan' },
        { status: 403 }
      )
    }

    try {
      const parsed = await parseRequest(createCredentialSetInvitationContract, req, context, {
        validationErrorResponse: (error) =>
          NextResponse.json({ error: getValidationErrorMessage(error) }, { status: 400 }),
      })
      if (!parsed.success) return parsed.response

      const { id } = parsed.data.params
      const { email } = parsed.data.body

      const result = await getCredentialSetWithAccess(id, session.user.id)

      if (!result) {
        return NextResponse.json({ error: 'Credential set not found' }, { status: 404 })
      }

      if (result.role !== 'admin' && result.role !== 'owner') {
        return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 })
      }

      const token = generateId()
      const expiresAt = new Date()
      expiresAt.setDate(expiresAt.getDate() + 7)

      const invitation = {
        id: generateId(),
        credentialSetId: id,
        email: email || null,
        token,
        invitedBy: session.user.id,
        status: 'pending' as const,
        expiresAt,
        createdAt: new Date(),
      }

      await db.insert(credentialSetInvitation).values(invitation)

      const inviteUrl = `${getBaseUrl()}/credential-account/${token}`

      // Send email if email address was provided
      if (email) {
        try {
          // Get inviter name
          const [inviter] = await db
            .select({ name: user.name })
            .from(user)
            .where(eq(user.id, session.user.id))
            .limit(1)

          // Get organization name
          const [org] = await db
            .select({ name: organization.name })
            .from(organization)
            .where(eq(organization.id, result.set.organizationId))
            .limit(1)

          const provider = (result.set.providerId as 'google-email' | 'outlook') || 'google-email'
          const emailHtml = await renderPollingGroupInvitationEmail({
            inviterName: inviter?.name || 'A team member',
            organizationName: org?.name || 'your organization',
            pollingGroupName: result.set.name,
            provider,
            inviteLink: inviteUrl,
          })

          const emailResult = await sendEmail({
            to: email,
            subject: getEmailSubject('polling-group-invitation'),
            html: emailHtml,
            emailType: 'transactional',
          })

          if (!emailResult.success) {
            logger.warn('Failed to send invitation email', {
              email,
              error: emailResult.message,
            })
          }
        } catch (emailError) {
          logger.error('Error sending invitation email', emailError)
          // Don't fail the invitation creation if email fails
        }
      }

      logger.info('Created credential set invitation', {
        credentialSetId: id,
        invitationId: invitation.id,
        userId: session.user.id,
        emailSent: !!email,
      })

      recordAudit({
        workspaceId: null,
        actorId: session.user.id,
        action: AuditAction.CREDENTIAL_SET_INVITATION_CREATED,
        resourceType: AuditResourceType.CREDENTIAL_SET,
        resourceId: id,
        actorName: session.user.name ?? undefined,
        actorEmail: session.user.email ?? undefined,
        resourceName: result.set.name,
        description: `Created invitation for credential set "${result.set.name}"${email ? ` to ${email}` : ''}`,
        metadata: {
          invitationId: invitation.id,
          targetEmail: email || undefined,
          providerId: result.set.providerId,
          credentialSetName: result.set.name,
        },
        request: req,
      })

      return NextResponse.json({
        invitation: {
          ...invitation,
          inviteUrl,
        },
      })
    } catch (error) {
      logger.error('Error creating invitation', error)
      return NextResponse.json({ error: 'Failed to create invitation' }, { status: 500 })
    }
  }
)

export const DELETE = withRouteHandler(
  async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => {
    const session = await getSession()

    if (!session?.user?.id) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
    }

    // Check plan access (team/enterprise) or env var override
    const hasAccess = await hasCredentialSetsAccess(session.user.id)
    if (!hasAccess) {
      return NextResponse.json(
        { error: 'Credential sets require a Team or Enterprise plan' },
        { status: 403 }
      )
    }

    const { id } = await params
    const { searchParams } = new URL(req.url)
    const validation = cancelCredentialSetInvitationQuerySchema.safeParse({
      invitationId: searchParams.get('invitationId') ?? '',
    })

    if (!validation.success) {
      return NextResponse.json(
        { error: getValidationErrorMessage(validation.error) },
        { status: 400 }
      )
    }

    const { invitationId } = validation.data

    try {
      const result = await getCredentialSetWithAccess(id, session.user.id)

      if (!result) {
        return NextResponse.json({ error: 'Credential set not found' }, { status: 404 })
      }

      if (result.role !== 'admin' && result.role !== 'owner') {
        return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 })
      }

      const [revokedInvitation] = await db
        .update(credentialSetInvitation)
        .set({ status: 'cancelled' })
        .where(
          and(
            eq(credentialSetInvitation.id, invitationId),
            eq(credentialSetInvitation.credentialSetId, id)
          )
        )
        .returning({ email: credentialSetInvitation.email })

      recordAudit({
        workspaceId: null,
        actorId: session.user.id,
        action: AuditAction.CREDENTIAL_SET_INVITATION_REVOKED,
        resourceType: AuditResourceType.CREDENTIAL_SET,
        resourceId: id,
        actorName: session.user.name ?? undefined,
        actorEmail: session.user.email ?? undefined,
        resourceName: result.set.name,
        description: `Revoked invitation "${invitationId}" for credential set "${result.set.name}"`,
        metadata: { targetEmail: revokedInvitation?.email ?? undefined },
        request: req,
      })

      return NextResponse.json({ success: true })
    } catch (error) {
      logger.error('Error cancelling invitation', error)
      return NextResponse.json({ error: 'Failed to cancel invitation' }, { status: 500 })
    }
  }
)
