import { and, eq, inArray, like, lt } from 'drizzle-orm'
import type { UserId } from '@zync/types'
import type { Db, DbTx } from '../client'
import { auditLog } from './_audit-forward'
import { roles, tenantMemberships, tenants, users } from '../schema'
import { SYSTEM_ROLE_NAMES, type SystemRoleName } from '../seed/permission-keys'
import { completeEmailVerificationTx } from './auth-writes'

const E2E_EMAIL_DOMAIN = 'zync.invalid'
const E2E_TENANT_PREFIX = 'E2E '
export const E2E_FIXTURE_TTL_MS = 24 * 60 * 60 * 1_000

export class E2eFixtureConflictError extends Error {
  constructor() {
    super('E2E fixture namespace already exists')
    this.name = 'E2eFixtureConflictError'
  }
}

export interface E2eRoleFixtureUser {
  id: string
  email: string
  role: SystemRoleName
}

export interface E2eRoleFixture {
  tenantId: string
  tenantSlug: string
  users: E2eRoleFixtureUser[]
}

interface FixtureIdentity {
  runId: string
  tenantId: string
  ownerId: string
}

function fixtureEmail(runId: string, role: SystemRoleName): string {
  return `e2e+${runId}-${role.toLowerCase()}@${E2E_EMAIL_DOMAIN}`
}

function fixtureTenantName(runId: string): string {
  return `${E2E_TENANT_PREFIX}${runId}`
}

function runIdFromOwnerEmail(email: string): string | null {
  const match = /^e2e\+([a-z0-9][a-z0-9-]{1,63})-owner@zync\.invalid$/.exec(email)
  return match?.[1] ?? null
}

function isUniqueViolation(error: unknown): boolean {
  return !!error && typeof error === 'object' && 'code' in error &&
    (error as { code?: string }).code === '23505'
}

async function findExactFixture(tx: Db | DbTx, runId: string): Promise<FixtureIdentity | null> {
  const [fixture] = await tx
    .select({
      tenantId: tenants.id,
      tenantName: tenants.name,
      ownerId: users.id,
      ownerEmail: users.email,
    })
    .from(users)
    .innerJoin(tenantMemberships, eq(tenantMemberships.userId, users.id))
    .innerJoin(tenants, eq(tenants.id, tenantMemberships.tenantId))
    .innerJoin(roles, eq(roles.id, tenantMemberships.roleId))
    .where(and(
      eq(users.email, fixtureEmail(runId, 'OWNER')),
      eq(roles.name, 'OWNER'),
      eq(tenants.name, fixtureTenantName(runId)),
    ))
    .limit(1)

  if (
    !fixture ||
    fixture.tenantName !== fixtureTenantName(runId) ||
    runIdFromOwnerEmail(fixture.ownerEmail) !== runId
  ) return null
  return { runId, tenantId: fixture.tenantId, ownerId: fixture.ownerId }
}

async function removeExactFixtureTx(tx: DbTx, fixture: FixtureIdentity): Promise<void> {
  const emails = SYSTEM_ROLE_NAMES.map((role) => fixtureEmail(fixture.runId, role))
  await tx.insert(auditLog).values({
    tenantId: fixture.tenantId,
    actorId: fixture.ownerId,
    actorType: 'user',
    entityType: 'e2e_fixture',
    entityId: fixture.tenantId,
    action: 'e2e_fixture.deleted',
  })
  await tx.delete(tenants).where(eq(tenants.id, fixture.tenantId))
  await tx.delete(users).where(inArray(users.email, emails))
}

async function cleanupStaleFixturesTx(tx: DbTx, cutoff: Date): Promise<void> {
  const candidates = await tx
    .select({
      tenantId: tenants.id,
      tenantName: tenants.name,
      ownerId: users.id,
      ownerEmail: users.email,
    })
    .from(users)
    .innerJoin(tenantMemberships, eq(tenantMemberships.userId, users.id))
    .innerJoin(tenants, eq(tenants.id, tenantMemberships.tenantId))
    .innerJoin(roles, eq(roles.id, tenantMemberships.roleId))
    .where(and(
      like(users.email, 'e2e+%-owner@zync.invalid'),
      like(tenants.name, `${E2E_TENANT_PREFIX}%`),
      eq(roles.name, 'OWNER'),
      lt(tenants.createdAt, cutoff),
    ))

  for (const candidate of candidates) {
    const runId = runIdFromOwnerEmail(candidate.ownerEmail)
    if (!runId || candidate.tenantName !== fixtureTenantName(runId)) continue
    await removeExactFixtureTx(tx, {
      runId,
      tenantId: candidate.tenantId,
      ownerId: candidate.ownerId,
    })
  }
}

export async function provisionE2eRoleFixture(
  db: Db,
  input: { runId: string; passwordHash: string; now?: Date },
): Promise<E2eRoleFixture> {
  try {
    return await db.transaction(async (tx) => {
      const now = input.now ?? new Date()
      await cleanupStaleFixturesTx(tx, new Date(now.getTime() - E2E_FIXTURE_TTL_MS))

      const [owner] = await tx
        .insert(users)
        .values({
          email: fixtureEmail(input.runId, 'OWNER'),
          passwordHash: input.passwordHash,
          name: 'E2E OWNER',
          emailVerifiedAt: now,
        })
        .returning({ id: users.id })

      const tenant = await completeEmailVerificationTx(tx, {
        userId: owner!.id as UserId,
        tenantName: fixtureTenantName(input.runId),
        markEmailVerified: false,
      })

      await tx
        .update(tenants)
        .set({ tier: 'business', onboardingCompleted: true, onboardingStep: 5 })
        .where(eq(tenants.id, tenant.tenantId))

      const roleRows = await tx
        .select({ id: roles.id, name: roles.name })
        .from(roles)
        .where(eq(roles.tenantId, tenant.tenantId))
      const roleIdByName = new Map(roleRows.map((role) => [role.name, role.id]))
      for (const role of SYSTEM_ROLE_NAMES) {
        if (!roleIdByName.has(role)) throw new Error(`Missing system role: ${role}`)
      }

      const nonOwnerRoles = SYSTEM_ROLE_NAMES.filter((role) => role !== 'OWNER')
      const inserted = await tx
        .insert(users)
        .values(nonOwnerRoles.map((role) => ({
          email: fixtureEmail(input.runId, role),
          passwordHash: input.passwordHash,
          name: `E2E ${role}`,
          emailVerifiedAt: now,
        })))
        .returning({ id: users.id, email: users.email })
      const idByEmail = new Map(inserted.map((user) => [user.email, user.id]))

      await tx.insert(tenantMemberships).values(nonOwnerRoles.map((role) => ({
        userId: idByEmail.get(fixtureEmail(input.runId, role))!,
        tenantId: tenant.tenantId,
        roleId: roleIdByName.get(role)!,
        status: 'active',
        isAccountant: role === 'ACCOUNTANT',
      })))
      await tx.insert(auditLog).values({
        tenantId: tenant.tenantId,
        actorId: owner!.id,
        actorType: 'user',
        entityType: 'e2e_fixture',
        entityId: tenant.tenantId,
        action: 'e2e_fixture.created',
      })

      return {
        tenantId: tenant.tenantId,
        tenantSlug: tenant.tenantSlug,
        users: SYSTEM_ROLE_NAMES.map((role) => ({
          id: role === 'OWNER' ? owner!.id : idByEmail.get(fixtureEmail(input.runId, role))!,
          email: fixtureEmail(input.runId, role),
          role,
        })),
      }
    })
  } catch (error) {
    if (isUniqueViolation(error)) throw new E2eFixtureConflictError()
    throw error
  }
}

export async function cleanupE2eRoleFixture(db: Db, runId: string): Promise<boolean> {
  return db.transaction(async (tx) => {
    const fixture = await findExactFixture(tx, runId)
    if (!fixture) return false
    await removeExactFixtureTx(tx, fixture)
    return true
  })
}
