import { db } from '@sim/db'
import {
  invitation,
  member,
  organization,
  user,
  type WorkspaceInvitationStatus,
  workspace,
  workspaceInvitation,
} from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, inArray, isNull, or } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import {
  getEmailSubject,
  renderBatchInvitationEmail,
  renderInvitationEmail,
} from '@/components/emails'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import {
  validateBulkInvitations,
  validateSeatAvailability,
} from '@/lib/billing/validation/seat-management'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { generateId } from '@/lib/core/utils/uuid'
import { sendEmail } from '@/lib/messaging/email/mailer'
import { quickValidateEmail } from '@/lib/messaging/email/validation'
import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils'
import {
  InvitationsNotAllowedError,
  validateInvitationsAllowed,
} from '@/ee/access-control/utils/permission-check'

const logger = createLogger('OrganizationInvitations')

interface WorkspaceInvitation {
  workspaceId: string
  permission: 'admin' | 'write' | 'read'
}

/**
 * GET /api/organizations/[id]/invitations
 * Get all pending invitations for an organization
 */
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  try {
    const session = await getSession()

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

    const { id: organizationId } = await params

    const memberEntry = await db
      .select()
      .from(member)
      .where(and(eq(member.organizationId, organizationId), eq(member.userId, session.user.id)))
      .limit(1)

    if (memberEntry.length === 0) {
      return NextResponse.json(
        { error: 'Forbidden - Not a member of this organization' },
        { status: 403 }
      )
    }

    const userRole = memberEntry[0].role
    const hasAdminAccess = ['owner', 'admin'].includes(userRole)

    if (!hasAdminAccess) {
      return NextResponse.json({ error: 'Forbidden - Admin access required' }, { status: 403 })
    }

    const invitations = await db
      .select({
        id: invitation.id,
        email: invitation.email,
        role: invitation.role,
        status: invitation.status,
        expiresAt: invitation.expiresAt,
        createdAt: invitation.createdAt,
        inviterName: user.name,
        inviterEmail: user.email,
      })
      .from(invitation)
      .leftJoin(user, eq(invitation.inviterId, user.id))
      .where(eq(invitation.organizationId, organizationId))
      .orderBy(invitation.createdAt)

    return NextResponse.json({
      success: true,
      data: {
        invitations,
        userRole,
      },
    })
  } catch (error) {
    logger.error('Failed to get organization invitations', {
      organizationId: (await params).id,
      error,
    })

    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}

/**
 * POST /api/organizations/[id]/invitations
 * Create organization invitations with optional validation and batch workspace invitations
 * Query parameters:
 * - ?validate=true - Only validate, don't send invitations
 * - ?batch=true - Include workspace invitations
 */
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  try {
    const session = await getSession()

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

    await validateInvitationsAllowed(session.user.id)

    const { id: organizationId } = await params
    const url = new URL(request.url)
    const validateOnly = url.searchParams.get('validate') === 'true'
    const isBatch = url.searchParams.get('batch') === 'true'

    const body = await request.json()
    const { email, emails, role = 'member', workspaceInvitations } = body

    const invitationEmails = email ? [email] : emails

    if (!invitationEmails || !Array.isArray(invitationEmails) || invitationEmails.length === 0) {
      return NextResponse.json({ error: 'Email or emails array is required' }, { status: 400 })
    }

    if (!['member', 'admin'].includes(role)) {
      return NextResponse.json({ error: 'Invalid role' }, { status: 400 })
    }

    const memberEntry = await db
      .select()
      .from(member)
      .where(and(eq(member.organizationId, organizationId), eq(member.userId, session.user.id)))
      .limit(1)

    if (memberEntry.length === 0) {
      return NextResponse.json(
        { error: 'Forbidden - Not a member of this organization' },
        { status: 403 }
      )
    }

    if (!['owner', 'admin'].includes(memberEntry[0].role)) {
      return NextResponse.json({ error: 'Forbidden - Admin access required' }, { status: 403 })
    }

    if (validateOnly) {
      const validationResult = await validateBulkInvitations(organizationId, invitationEmails)

      logger.info('Invitation validation completed', {
        organizationId,
        userId: session.user.id,
        emailCount: invitationEmails.length,
        result: validationResult,
      })

      return NextResponse.json({
        success: true,
        data: validationResult,
        validatedBy: session.user.id,
        validatedAt: new Date().toISOString(),
      })
    }

    const seatValidation = await validateSeatAvailability(organizationId, invitationEmails.length)

    if (!seatValidation.canInvite) {
      return NextResponse.json(
        {
          error: seatValidation.reason,
          seatInfo: {
            currentSeats: seatValidation.currentSeats,
            maxSeats: seatValidation.maxSeats,
            availableSeats: seatValidation.availableSeats,
            seatsRequested: invitationEmails.length,
          },
        },
        { status: 400 }
      )
    }

    const organizationEntry = await db
      .select({ name: organization.name })
      .from(organization)
      .where(eq(organization.id, organizationId))
      .limit(1)

    if (organizationEntry.length === 0) {
      return NextResponse.json({ error: 'Organization not found' }, { status: 404 })
    }

    const processedEmails = invitationEmails
      .map((email: string) => {
        const normalized = email.trim().toLowerCase()
        const validation = quickValidateEmail(normalized)
        return validation.isValid ? normalized : null
      })
      .filter(Boolean) as string[]

    if (processedEmails.length === 0) {
      return NextResponse.json({ error: 'No valid emails provided' }, { status: 400 })
    }

    const validWorkspaceInvitations: WorkspaceInvitation[] = []
    if (isBatch && workspaceInvitations && workspaceInvitations.length > 0) {
      for (const wsInvitation of workspaceInvitations) {
        const canInvite = await hasWorkspaceAdminAccess(session.user.id, wsInvitation.workspaceId)

        if (!canInvite) {
          return NextResponse.json(
            {
              error: `You don't have permission to invite users to workspace ${wsInvitation.workspaceId}`,
            },
            { status: 403 }
          )
        }

        validWorkspaceInvitations.push(wsInvitation)
      }
    }

    const existingMembers = await db
      .select({ userEmail: user.email })
      .from(member)
      .innerJoin(user, eq(member.userId, user.id))
      .where(eq(member.organizationId, organizationId))

    const existingEmails = existingMembers.map((m) => m.userEmail)
    const newEmails = processedEmails.filter((email: string) => !existingEmails.includes(email))

    const existingInvitations = await db
      .select({ email: invitation.email })
      .from(invitation)
      .where(and(eq(invitation.organizationId, organizationId), eq(invitation.status, 'pending')))

    const pendingEmails = existingInvitations.map((i) => i.email)
    const emailsToInvite = newEmails.filter((email: string) => !pendingEmails.includes(email))

    if (emailsToInvite.length === 0) {
      const isSingleEmail = processedEmails.length === 1
      const existingMembersEmails = processedEmails.filter((email: string) =>
        existingEmails.includes(email)
      )
      const pendingInvitationEmails = processedEmails.filter((email: string) =>
        pendingEmails.includes(email)
      )

      if (isSingleEmail) {
        if (existingMembersEmails.length > 0) {
          return NextResponse.json(
            {
              error: 'Failed to send invitation. User is already a part of the organization.',
            },
            { status: 400 }
          )
        }
        if (pendingInvitationEmails.length > 0) {
          return NextResponse.json(
            {
              error:
                'Failed to send invitation. A pending invitation already exists for this email.',
            },
            { status: 400 }
          )
        }
      }

      return NextResponse.json(
        {
          error: 'All emails are already members or have pending invitations.',
          details: {
            existingMembers: existingMembersEmails,
            pendingInvitations: pendingInvitationEmails,
          },
        },
        { status: 400 }
      )
    }

    const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
    const invitationsToCreate = emailsToInvite.map((email: string) => ({
      id: generateId(),
      email,
      inviterId: session.user.id,
      organizationId,
      role,
      status: 'pending' as const,
      expiresAt,
      createdAt: new Date(),
    }))

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

    const workspaceInvitationIds: string[] = []
    if (isBatch && validWorkspaceInvitations.length > 0) {
      for (const email of emailsToInvite) {
        const orgInviteForEmail = invitationsToCreate.find((inv) => inv.email === email)
        for (const wsInvitation of validWorkspaceInvitations) {
          const wsInvitationId = generateId()
          const token = generateId()

          await db.insert(workspaceInvitation).values({
            id: wsInvitationId,
            workspaceId: wsInvitation.workspaceId,
            email,
            inviterId: session.user.id,
            role: 'member',
            status: 'pending',
            token,
            permissions: wsInvitation.permission,
            orgInvitationId: orgInviteForEmail?.id,
            expiresAt,
            createdAt: new Date(),
            updatedAt: new Date(),
          })

          workspaceInvitationIds.push(wsInvitationId)
        }
      }
    }

    const inviter = await db
      .select({ name: user.name })
      .from(user)
      .where(eq(user.id, session.user.id))
      .limit(1)

    for (const email of emailsToInvite) {
      const orgInvitation = invitationsToCreate.find((inv) => inv.email === email)
      if (!orgInvitation) continue

      let emailResult
      if (isBatch && validWorkspaceInvitations.length > 0) {
        const workspaceDetails = await db
          .select({
            id: workspace.id,
            name: workspace.name,
          })
          .from(workspace)
          .where(
            inArray(
              workspace.id,
              validWorkspaceInvitations.map((w) => w.workspaceId)
            )
          )

        const workspaceInvitationsWithNames = validWorkspaceInvitations.map((wsInv) => ({
          workspaceId: wsInv.workspaceId,
          workspaceName:
            workspaceDetails.find((w) => w.id === wsInv.workspaceId)?.name || 'Unknown Workspace',
          permission: wsInv.permission,
        }))

        const emailHtml = await renderBatchInvitationEmail(
          inviter[0]?.name || 'Someone',
          organizationEntry[0]?.name || 'organization',
          role,
          workspaceInvitationsWithNames,
          `${getBaseUrl()}/invite/${orgInvitation.id}`
        )

        emailResult = await sendEmail({
          to: email,
          subject: getEmailSubject('batch-invitation'),
          html: emailHtml,
          emailType: 'transactional',
        })
      } else {
        const emailHtml = await renderInvitationEmail(
          inviter[0]?.name || 'Someone',
          organizationEntry[0]?.name || 'organization',
          `${getBaseUrl()}/invite/${orgInvitation.id}`
        )

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

      if (!emailResult.success) {
        logger.error('Failed to send invitation email', {
          email,
          error: emailResult.message,
        })
      }
    }

    logger.info('Organization invitations created', {
      organizationId,
      invitedBy: session.user.id,
      invitationCount: invitationsToCreate.length,
      emails: emailsToInvite,
      role,
      isBatch,
      workspaceInvitationCount: workspaceInvitationIds.length,
    })

    for (const inv of invitationsToCreate) {
      recordAudit({
        workspaceId: null,
        actorId: session.user.id,
        action: AuditAction.ORG_INVITATION_CREATED,
        resourceType: AuditResourceType.ORGANIZATION,
        resourceId: organizationId,
        actorName: session.user.name ?? undefined,
        actorEmail: session.user.email ?? undefined,
        resourceName: organizationEntry[0]?.name,
        description: `Invited ${inv.email} to organization as ${role}`,
        metadata: { invitationId: inv.id, targetEmail: inv.email, targetRole: role },
        request,
      })
    }

    return NextResponse.json({
      success: true,
      message: `${invitationsToCreate.length} invitation(s) sent successfully`,
      data: {
        invitationsSent: invitationsToCreate.length,
        invitedEmails: emailsToInvite,
        existingMembers: processedEmails.filter((email: string) => existingEmails.includes(email)),
        pendingInvitations: processedEmails.filter((email: string) =>
          pendingEmails.includes(email)
        ),
        invalidEmails: invitationEmails.filter(
          (email: string) => !quickValidateEmail(email.trim().toLowerCase()).isValid
        ),
        workspaceInvitations: isBatch ? validWorkspaceInvitations.length : 0,
        seatInfo: {
          seatsUsed: seatValidation.currentSeats + invitationsToCreate.length,
          maxSeats: seatValidation.maxSeats,
          availableSeats: seatValidation.availableSeats - invitationsToCreate.length,
        },
      },
    })
  } catch (error) {
    if (error instanceof InvitationsNotAllowedError) {
      return NextResponse.json({ error: error.message }, { status: 403 })
    }

    logger.error('Failed to create organization invitations', {
      organizationId: (await params).id,
      error,
    })

    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}

/**
 * DELETE /api/organizations/[id]/invitations?invitationId=...
 * Cancel a pending invitation
 */
export async function DELETE(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  try {
    const session = await getSession()

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

    const { id: organizationId } = await params
    const url = new URL(request.url)
    const invitationId = url.searchParams.get('invitationId')

    if (!invitationId) {
      return NextResponse.json(
        { error: 'Invitation ID is required as query parameter' },
        { status: 400 }
      )
    }

    const memberEntry = await db
      .select()
      .from(member)
      .where(and(eq(member.organizationId, organizationId), eq(member.userId, session.user.id)))
      .limit(1)

    if (memberEntry.length === 0) {
      return NextResponse.json(
        { error: 'Forbidden - Not a member of this organization' },
        { status: 403 }
      )
    }

    if (!['owner', 'admin'].includes(memberEntry[0].role)) {
      return NextResponse.json({ error: 'Forbidden - Admin access required' }, { status: 403 })
    }

    const result = await db
      .update(invitation)
      .set({ status: 'cancelled' })
      .where(
        and(
          eq(invitation.id, invitationId),
          eq(invitation.organizationId, organizationId),
          or(eq(invitation.status, 'pending'), eq(invitation.status, 'rejected'))
        )
      )
      .returning()

    if (result.length === 0) {
      return NextResponse.json(
        { error: 'Invitation not found or already processed' },
        { status: 404 }
      )
    }

    await db
      .update(workspaceInvitation)
      .set({ status: 'cancelled' as WorkspaceInvitationStatus })
      .where(eq(workspaceInvitation.orgInvitationId, invitationId))

    await db
      .update(workspaceInvitation)
      .set({ status: 'cancelled' as WorkspaceInvitationStatus })
      .where(
        and(
          isNull(workspaceInvitation.orgInvitationId),
          eq(workspaceInvitation.email, result[0].email),
          eq(workspaceInvitation.status, 'pending' as WorkspaceInvitationStatus),
          eq(workspaceInvitation.inviterId, session.user.id)
        )
      )

    logger.info('Organization invitation cancelled', {
      organizationId,
      invitationId,
      cancelledBy: session.user.id,
      email: result[0].email,
    })

    recordAudit({
      workspaceId: null,
      actorId: session.user.id,
      action: AuditAction.ORG_INVITATION_REVOKED,
      resourceType: AuditResourceType.ORGANIZATION,
      resourceId: organizationId,
      actorName: session.user.name ?? undefined,
      actorEmail: session.user.email ?? undefined,
      description: `Revoked organization invitation for ${result[0].email}`,
      metadata: { invitationId, targetEmail: result[0].email },
      request,
    })

    return NextResponse.json({
      success: true,
      message: 'Invitation cancelled successfully',
    })
  } catch (error) {
    logger.error('Failed to cancel organization invitation', {
      organizationId: (await params).id,
      error,
    })

    return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
  }
}
