/**
 * @vitest-environment node
 */
import { auditMock, createSession, loggerMock } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'

const {
  mockDbState,
  mockGetSession,
  mockSetActiveOrganizationForCurrentSession,
  mockCreateOrganizationForTeamPlan,
  mockEnsureOrganizationForTeamSubscription,
  mockAttachOwnedWorkspacesToOrganization,
  WorkspaceOrganizationMembershipConflictError,
} = vi.hoisted(() => ({
  mockDbState: {
    selectResults: [] as any[],
  },
  mockGetSession: vi.fn(),
  mockSetActiveOrganizationForCurrentSession: vi.fn().mockResolvedValue(undefined),
  mockCreateOrganizationForTeamPlan: vi.fn(),
  mockEnsureOrganizationForTeamSubscription: vi.fn(),
  mockAttachOwnedWorkspacesToOrganization: vi.fn().mockResolvedValue(undefined),
  WorkspaceOrganizationMembershipConflictError: class WorkspaceOrganizationMembershipConflictError extends Error {},
}))

vi.mock('@sim/db', () => ({
  db: {
    select: vi.fn().mockImplementation(() => {
      const chain: any = {}
      chain.from = vi.fn().mockReturnValue(chain)
      chain.where = vi.fn().mockReturnValue(chain)
      chain.limit = vi
        .fn()
        .mockImplementation(() => Promise.resolve(mockDbState.selectResults.shift() ?? []))
      chain.then = vi
        .fn()
        .mockImplementation((callback: (rows: any[]) => any) =>
          Promise.resolve(callback(mockDbState.selectResults.shift() ?? []))
        )
      return chain
    }),
  },
}))

vi.mock('@sim/db/schema', () => ({
  member: {
    organizationId: 'member.organizationId',
    role: 'member.role',
    userId: 'member.userId',
  },
  organization: {
    id: 'organization.id',
    name: 'organization.name',
  },
  subscription: {
    id: 'subscription.id',
    plan: 'subscription.plan',
    referenceId: 'subscription.referenceId',
    status: 'subscription.status',
    seats: 'subscription.seats',
  },
}))

vi.mock('drizzle-orm', () => ({
  and: vi.fn((...conditions: unknown[]) => ({ type: 'and', conditions })),
  eq: vi.fn((field: unknown, value: unknown) => ({ field, value })),
  inArray: vi.fn((field: unknown, value: unknown[]) => ({ field, value })),
  or: vi.fn((...conditions: unknown[]) => ({ type: 'or', conditions })),
}))

vi.mock('@sim/logger', () => loggerMock)

vi.mock('@sim/audit', () => auditMock)

vi.mock('@/lib/auth', () => ({
  getSession: mockGetSession,
}))

vi.mock('@/lib/auth/active-organization', () => ({
  setActiveOrganizationForCurrentSession: mockSetActiveOrganizationForCurrentSession,
}))

vi.mock('@/lib/billing/organization', () => ({
  createOrganizationForTeamPlan: mockCreateOrganizationForTeamPlan,
  ensureOrganizationForTeamSubscription: mockEnsureOrganizationForTeamSubscription,
}))

vi.mock('@/lib/billing/organizations/create-organization', () => ({
  OrganizationSlugInvalidError: class OrganizationSlugInvalidError extends Error {},
  OrganizationSlugTakenError: class OrganizationSlugTakenError extends Error {},
}))

vi.mock('@/lib/billing/plan-helpers', () => ({
  isOrgPlan: (plan: string) => plan === 'team' || plan === 'enterprise',
}))

vi.mock('@/lib/billing/subscriptions/utils', () => ({
  ENTITLED_SUBSCRIPTION_STATUSES: ['active', 'trialing'],
}))

vi.mock('@/lib/workspaces/organization-workspaces', () => ({
  attachOwnedWorkspacesToOrganization: mockAttachOwnedWorkspacesToOrganization,
  WorkspaceOrganizationMembershipConflictError,
}))

import { POST } from '@/app/api/organizations/route'

describe('POST /api/organizations', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockDbState.selectResults = []
  })

  it('recovers an owner org when the subscription was already moved onto the organization', async () => {
    mockGetSession.mockResolvedValue(
      createSession({
        userId: 'user-1',
        email: 'owner@example.com',
        name: 'Owner',
      })
    )
    mockDbState.selectResults = [
      [{ organizationId: 'legacy-org-id', role: 'owner' }],
      [{ id: 'sub-1', plan: 'team', referenceId: 'legacy-org-id', status: 'active', seats: 5 }],
    ]

    const response = await POST(
      new Request('http://localhost/api/organizations', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: 'Recovered Org' }),
      })
    )

    expect(response.status).toBe(200)
    await expect(response.json()).resolves.toEqual({
      success: true,
      organizationId: 'legacy-org-id',
      created: false,
    })
    expect(mockAttachOwnedWorkspacesToOrganization).toHaveBeenCalledWith({
      ownerUserId: 'user-1',
      organizationId: 'legacy-org-id',
    })
    expect(mockCreateOrganizationForTeamPlan).not.toHaveBeenCalled()
    expect(mockEnsureOrganizationForTeamSubscription).not.toHaveBeenCalled()
    expect(mockSetActiveOrganizationForCurrentSession).toHaveBeenCalledWith('legacy-org-id')
    expect(auditMock.recordAudit).not.toHaveBeenCalled()
  })

  it('recovers an owner org when the subscription is still linked to the user', async () => {
    mockGetSession.mockResolvedValue(
      createSession({
        userId: 'user-1',
        email: 'owner@example.com',
        name: 'Owner',
      })
    )
    mockEnsureOrganizationForTeamSubscription.mockResolvedValue({
      id: 'sub-1',
      plan: 'team',
      referenceId: 'legacy-org-id',
      status: 'active',
      seats: 5,
    })
    mockDbState.selectResults = [
      [{ organizationId: 'legacy-org-id', role: 'owner' }],
      [{ id: 'sub-1', plan: 'team', referenceId: 'user-1', status: 'active', seats: 5 }],
    ]

    const response = await POST(
      new Request('http://localhost/api/organizations', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: 'Recovered Org' }),
      })
    )

    expect(response.status).toBe(200)
    await expect(response.json()).resolves.toEqual({
      success: true,
      organizationId: 'legacy-org-id',
      created: false,
    })
    expect(mockEnsureOrganizationForTeamSubscription).toHaveBeenCalledWith({
      id: 'sub-1',
      plan: 'team',
      referenceId: 'user-1',
      status: 'active',
      seats: 5,
    })
    expect(mockAttachOwnedWorkspacesToOrganization).not.toHaveBeenCalled()
    expect(mockCreateOrganizationForTeamPlan).not.toHaveBeenCalled()
  })

  it('still blocks users who are only members of another organization', async () => {
    mockGetSession.mockResolvedValue(
      createSession({
        userId: 'user-1',
        email: 'member@example.com',
        name: 'Member',
      })
    )
    mockDbState.selectResults = [[{ organizationId: 'org-1', role: 'member' }]]

    const response = await POST(
      new Request('http://localhost/api/organizations', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: 'Blocked Org' }),
      })
    )

    expect(response.status).toBe(409)
    await expect(response.json()).resolves.toEqual({
      error:
        'You are already a member of an organization. Leave your current organization before creating a new one.',
    })
    expect(mockEnsureOrganizationForTeamSubscription).not.toHaveBeenCalled()
    expect(mockCreateOrganizationForTeamPlan).not.toHaveBeenCalled()
    expect(mockAttachOwnedWorkspacesToOrganization).not.toHaveBeenCalled()
  })

  it('returns a conflict when existing shared workspace members block organization attachment', async () => {
    mockGetSession.mockResolvedValue(
      createSession({
        userId: 'user-1',
        email: 'owner@example.com',
        name: 'Owner',
      })
    )
    mockDbState.selectResults = [
      [{ organizationId: 'legacy-org-id', role: 'owner' }],
      [{ id: 'sub-1', plan: 'team', referenceId: 'legacy-org-id', status: 'active', seats: 5 }],
    ]
    mockAttachOwnedWorkspacesToOrganization.mockRejectedValueOnce(
      new WorkspaceOrganizationMembershipConflictError([
        { userId: 'user-2', organizationId: 'org-2' },
      ])
    )

    const response = await POST(
      new Request('http://localhost/api/organizations', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: 'Recovered Org' }),
      })
    )

    expect(response.status).toBe(409)
    await expect(response.json()).resolves.toEqual({
      error:
        'One or more members of your existing shared workspaces already belong to another organization. Remove them from those workspaces before converting them to organization-owned workspaces.',
    })
    expect(mockSetActiveOrganizationForCurrentSession).not.toHaveBeenCalled()
  })
})
