/**
 * RLS Test Context Manager
 * Manages isolated test data and user context switching for RLS integration tests
 *
 * Usage:
 * ```typescript
 * describe('RLS Policies - API Keys', () => {
 *   let ctx: RLSTestContext
 *
 *   beforeAll(async () => {
 *     ctx = new RLSTestContext()
 *     // Create test data
 *   })
 *
 *   afterAll(async () => {
 *     await ctx.cleanup()
 *   })
 * })
 * ```
 */

import { createClient, SupabaseClient } from '@supabase/supabase-js'
import jwt from 'jsonwebtoken'
import type { Database } from '../../supabase/database.types'

type Role = 'admin' | 'member'

interface UserWithWorkosId {
  id: string
  workos_user_id: string
  email: string
  is_platform_admin: boolean
}

interface PreProvisionedUser {
  id: string
  email: string
  is_platform_admin: boolean
}

interface Organization {
  id: string
  name: string
}

interface Membership {
  id: string
  user_id: string
  organization_id: string
  role_slug: Role
}

export class RLSTestContext {
  adminClient: SupabaseClient<Database>
  testPrefix: string
  createdIds: {
    users: string[]
    organizations: string[]
    memberships: string[]
    apiKeys: string[]
    invitations: string[]
    taskSchedules: string[]
    commandQueue: string[]
    sessions: string[]
    sessionMessages: string[]
    executionLogs: string[]
    executionMetrics: string[]
    notifications: string[]
    credentials: string[]
    activities: string[]
  }

  constructor() {
    // Verify test environment is configured
    if (!process.env.SUPABASE_URL) {
      throw new Error('SUPABASE_URL not configured in .env.development')
    }
    if (!process.env.SUPABASE_SERVICE_KEY) {
      throw new Error('SUPABASE_SERVICE_KEY not configured in .env.development')
    }
    if (!process.env.SUPABASE_JWT_SECRET) {
      throw new Error('SUPABASE_JWT_SECRET not configured in .env.development')
    }

    // Create admin client (bypasses RLS with service_role key)
    this.adminClient = createClient<Database>(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY)

    // Generate unique prefix for this test run to prevent collisions
    this.testPrefix = `test_${Date.now()}_${Math.random().toString(36).substring(7)}`

    // Track created resources for cleanup
    this.createdIds = {
      users: [],
      organizations: [],
      memberships: [],
      apiKeys: [],
      invitations: [],
      taskSchedules: [],
      commandQueue: [],
      sessions: [],
      sessionMessages: [],
      executionLogs: [],
      executionMetrics: [],
      notifications: [],
      credentials: [],
      activities: []
    }
  }

  /**
   * Create a test organization
   */
  async createOrganization(name: string): Promise<Organization> {
    // Generate a unique WorkOS org ID for test organizations
    const workosOrgId = `org_${this.testPrefix}_${Math.random().toString(36).substring(7)}`

    const { data, error } = await this.adminClient
      .from('organizations')
      .insert({
        workos_org_id: workosOrgId,
        name: `${this.testPrefix}_${name}`,
        is_test: true
      })
      .select()
      .single()

    if (error) {
      throw new Error(`Failed to create organization: ${error.message}`)
    }

    this.createdIds.organizations.push(data.id)
    return data
  }

  /**
   * Create a test user with a WorkOS user ID
   */
  async createUser(email: string, isPlatformAdmin = false): Promise<UserWithWorkosId> {
    // Generate a unique WorkOS user ID for this test user
    const workosUserId = `user_${this.testPrefix}_${Math.random().toString(36).substring(7)}`

    const { data, error } = await this.adminClient
      .from('users')
      .insert({
        workos_user_id: workosUserId,
        email: `${this.testPrefix}_${email}`,
        first_name: 'Test',
        last_name: 'User',
        is_platform_admin: isPlatformAdmin
      })
      .select('*')
      .single()

    if (error) {
      throw new Error(`Failed to create user: ${error.message}`)
    }

    this.createdIds.users.push(data.id)

    return {
      id: data.id,
      workos_user_id: workosUserId,
      email: data.email,
      is_platform_admin: (data as { is_platform_admin?: boolean }).is_platform_admin ?? false
    }
  }

  /**
   * Create a pre-provisioned test user (without WorkOS user ID)
   * Used for testing invitation flows where users are created before they sign up
   */
  async createPreProvisionedUser(email: string, isPlatformAdmin = false): Promise<PreProvisionedUser> {
    const { data, error } = await this.adminClient
      .from('users')
      .insert({
        workos_user_id: null, // Key difference: NULL for pre-provisioned
        email: `${this.testPrefix}_${email}`,
        first_name: 'Test',
        last_name: 'PreProvisioned',
        is_platform_admin: isPlatformAdmin
      })
      .select('*')
      .single()

    if (error) {
      throw new Error(`Failed to create pre-provisioned user: ${error.message}`)
    }

    this.createdIds.users.push(data.id)

    return {
      id: data.id,
      email: data.email,
      is_platform_admin: (data as { is_platform_admin?: boolean }).is_platform_admin ?? false
    }
  }

  /**
   * Create an organization membership
   */
  async createMembership(userId: string, organizationId: string, role: Role): Promise<Membership> {
    // Generate a unique WorkOS membership ID
    const workosMembershipId = `om_${this.testPrefix}_${Math.random().toString(36).substring(7)}`

    const { data, error } = await this.adminClient
      .from('org_memberships')
      .insert({
        user_id: userId,
        organization_id: organizationId,
        workos_membership_id: workosMembershipId,
        role_slug: role,
        membership_status: 'active'
      })
      .select()
      .single()

    if (error) {
      throw new Error(`Failed to create membership: ${error.message}`)
    }

    this.createdIds.memberships.push(data.id)

    await this.assignSystemRole(data.id, role)

    return {
      id: data.id,
      user_id: data.user_id,
      organization_id: data.organization_id,
      role_slug: data.role_slug as Role
    }
  }

  /**
   * Assign a system role to a membership via org_rol_assignments.
   * After the 2026-04-25 auth refactor, RLS policies read `effective_permissions[]`
   * (materialized by trigger from org_rol_assignments → org_rol_grants).
   * Without this assignment, role_slug is informational only and the membership
   * has zero permissions, causing all has_org_permission() checks to deny.
   */
  private async assignSystemRole(membershipId: string, slug: Role): Promise<void> {
    const { data: roleDef, error: roleErr } = await this.adminClient
      .from('org_rol_definitions')
      .select('id')
      .is('organization_id', null)
      .eq('slug', slug)
      .single()

    if (roleErr || !roleDef) {
      throw new Error(`Failed to look up system role '${slug}': ${roleErr?.message ?? 'not found'}`)
    }

    const { error: assignErr } = await this.adminClient
      .from('org_rol_assignments')
      .upsert({ membership_id: membershipId, role_id: roleDef.id }, { onConflict: 'membership_id,role_id' })

    if (assignErr) {
      throw new Error(`Failed to assign system role '${slug}' to membership: ${assignErr.message}`)
    }
  }

  /**
   * Create a pre-provisioned organization membership (without WorkOS membership ID)
   * Used for testing invitation flows where memberships are created before user accepts
   */
  async createPreProvisionedMembership(userId: string, organizationId: string, role: Role): Promise<Membership> {
    const { data, error } = await this.adminClient
      .from('org_memberships')
      .insert({
        user_id: userId,
        organization_id: organizationId,
        workos_membership_id: null, // Key difference: NULL for pre-provisioned
        role_slug: role,
        membership_status: 'active' // Pre-provisioned memberships are active from creation
      })
      .select()
      .single()

    if (error) {
      throw new Error(`Failed to create pre-provisioned membership: ${error.message}`)
    }

    this.createdIds.memberships.push(data.id)

    await this.assignSystemRole(data.id, role)

    return {
      id: data.id,
      user_id: data.user_id,
      organization_id: data.organization_id,
      role_slug: data.role_slug as Role
    }
  }

  /**
   * Generate a JWT token for a test user
   * Uses Supabase JWT secret so auth.jwt() in RLS policies can decode it
   */
  generateJWT(workosUserId: string, email?: string): string {
    if (!process.env.SUPABASE_JWT_SECRET) {
      throw new Error('SUPABASE_JWT_SECRET not configured')
    }

    const payload = {
      sub: workosUserId, // Supabase RLS policies use auth.jwt() ->> 'sub'
      aud: 'authenticated',
      role: 'authenticated',
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + 60 * 60, // 1 hour expiry
      ...(email && { email }) // Add email claim if provided (for pre-provisioned user RLS)
    }

    return jwt.sign(payload, process.env.SUPABASE_JWT_SECRET, {
      algorithm: 'HS256'
    })
  }

  /**
   * Create a Supabase client for a specific user (respects RLS)
   * This client will have the user's JWT token, so RLS policies will apply
   */
  createUserClient(workosUserId: string): SupabaseClient<Database> {
    if (!process.env.SUPABASE_URL || !process.env.SUPABASE_ANON_KEY) {
      throw new Error('Test environment not configured')
    }

    const token = this.generateJWT(workosUserId)

    // Create client with anon key and set auth token
    const client = createClient<Database>(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, {
      global: {
        headers: {
          Authorization: `Bearer ${token}`
        }
      }
    })

    return client
  }

  /**
   * Create a Supabase client for a pre-provisioned user (respects RLS)
   * Uses a dummy workos_user_id but includes the email claim for RLS matching
   * The email claim is what matters for pre-provisioned user RLS policies
   */
  createPreProvisionedUserClient(email: string): SupabaseClient<Database> {
    if (!process.env.SUPABASE_URL || !process.env.SUPABASE_ANON_KEY) {
      throw new Error('Test environment not configured')
    }

    // Use a dummy workos_user_id that won't match any real user
    // The email claim is what matters for pre-provisioned user RLS
    const dummyWorkosUserId = `preprov_${this.testPrefix}_${Math.random().toString(36).substring(7)}`
    const token = this.generateJWT(dummyWorkosUserId, email)

    // Create client with anon key and set auth token
    const client = createClient<Database>(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, {
      global: {
        headers: {
          Authorization: `Bearer ${token}`
        }
      }
    })

    return client
  }

  /** Create an organization with an admin user and authenticated client. */
  async createOrgWithAdmin(
    name: string,
    email: string
  ): Promise<{
    org: Organization
    user: UserWithWorkosId
    membership: Membership
    client: SupabaseClient<Database>
  }> {
    const org = await this.createOrganization(name)
    const user = await this.createUser(email, false)
    const membership = await this.createMembership(user.id, org.id, 'admin')
    const client = this.createUserClient(user.workos_user_id)
    return { org, user, membership, client }
  }

  /** Create two isolated organizations with admin users for cross-org isolation tests. */
  async createCrossOrgFixture(
    nameA = 'OrgA',
    nameB = 'OrgB'
  ): Promise<{
    orgA: Organization
    orgB: Organization
    userA: UserWithWorkosId
    userB: UserWithWorkosId
    membershipA: Membership
    membershipB: Membership
    clientA: SupabaseClient<Database>
    clientB: SupabaseClient<Database>
  }> {
    const orgA = await this.createOrganization(nameA)
    const orgB = await this.createOrganization(nameB)
    const userA = await this.createUser(`${nameA.toLowerCase()}Admin@test.com`, false)
    const userB = await this.createUser(`${nameB.toLowerCase()}Admin@test.com`, false)
    const membershipA = await this.createMembership(userA.id, orgA.id, 'admin')
    const membershipB = await this.createMembership(userB.id, orgB.id, 'admin')
    const clientA = this.createUserClient(userA.workos_user_id)
    const clientB = this.createUserClient(userB.workos_user_id)
    return { orgA, orgB, userA, userB, membershipA, membershipB, clientA, clientB }
  }

  /**
   * Clean up all test data created during the test run
   * Called in afterAll() to prevent test pollution
   *
   * Cleanup order respects foreign key dependencies (child tables before parent tables):
   *
   * Level 1 (Deepest dependencies):
   * - execution_metrics (FK: execution_logs)
   * - session_messages (FK: sessions)
   *
   * Level 2 (Mid-level dependencies):
   * - execution_logs (FK: sessions, users)
   * - task_schedules (FK: organizations)
   * - command_queue (FK: organizations, users)
   * - notifications (FK: organizations, users)
   * - credentials (FK: organizations, users)
   * - sessions (FK: organizations, users)
   * - activities (FK: organizations)
   *
   * Level 3 (Organization/User dependencies):
   * - invitations (FK: organizations, users)
   * - api_keys (FK: organizations)
   * - memberships (FK: organizations, users)
   *
   * Level 4 (Base tables):
   * - users
   * - organizations
   */
  async cleanup(): Promise<void> {
    const errors: string[] = []

    // LEVEL 1: Delete deepest child tables first

    // Delete execution_metrics (FK: execution_logs)
    if (this.createdIds.executionMetrics.length > 0) {
      const { error } = await this.adminClient
        .from('execution_metrics')
        .delete()
        .in('execution_id', this.createdIds.executionMetrics)

      if (error) {
        errors.push(`Failed to delete execution_metrics: ${error.message}`)
      }
    }

    // Delete session_messages (FK: sessions)
    if (this.createdIds.sessionMessages.length > 0) {
      const { error } = await this.adminClient
        .from('session_messages')
        .delete()
        .in('id', this.createdIds.sessionMessages)

      if (error) {
        errors.push(`Failed to delete session_messages: ${error.message}`)
      }
    }

    // LEVEL 2: Delete mid-level tables

    // Delete execution_logs (FK: sessions, users)
    if (this.createdIds.executionLogs.length > 0) {
      const { error } = await this.adminClient
        .from('execution_logs')
        .delete()
        .in('execution_id', this.createdIds.executionLogs)

      if (error) {
        errors.push(`Failed to delete execution_logs: ${error.message}`)
      }
    }

    // Delete task_schedules (FK: organizations)
    if (this.createdIds.taskSchedules.length > 0) {
      const { error } = await this.adminClient.from('task_schedules').delete().in('id', this.createdIds.taskSchedules)

      if (error) {
        errors.push(`Failed to delete task_schedules: ${error.message}`)
      }
    }

    // Delete command_queue (FK: organizations, users)
    if (this.createdIds.commandQueue.length > 0) {
      const { error } = await this.adminClient.from('command_queue').delete().in('id', this.createdIds.commandQueue)

      if (error) {
        errors.push(`Failed to delete command_queue: ${error.message}`)
      }
    }

    // Delete notifications (FK: organizations, users)
    if (this.createdIds.notifications.length > 0) {
      const { error } = await this.adminClient.from('notifications').delete().in('id', this.createdIds.notifications)

      if (error) {
        errors.push(`Failed to delete notifications: ${error.message}`)
      }
    }

    // Delete credentials (FK: organizations, users)
    if (this.createdIds.credentials.length > 0) {
      const { error } = await this.adminClient.from('credentials').delete().in('id', this.createdIds.credentials)

      if (error) {
        errors.push(`Failed to delete credentials: ${error.message}`)
      }
    }

    // Delete sessions (FK: organizations, users)
    if (this.createdIds.sessions.length > 0) {
      const { error } = await this.adminClient.from('sessions').delete().in('session_id', this.createdIds.sessions)

      if (error) {
        errors.push(`Failed to delete sessions: ${error.message}`)
      }
    }

    // Delete activities (FK: organizations)
    if (this.createdIds.activities.length > 0) {
      const { error } = await this.adminClient.from('activities').delete().in('id', this.createdIds.activities)

      if (error) {
        errors.push(`Failed to delete activities: ${error.message}`)
      }
    }

    // LEVEL 3: Delete organization/user relationship tables

    // Delete invitations
    if (this.createdIds.invitations.length > 0) {
      const { error } = await this.adminClient.from('org_invitations').delete().in('id', this.createdIds.invitations)

      if (error) {
        errors.push(`Failed to delete invitations: ${error.message}`)
      }
    }

    // Delete API keys
    if (this.createdIds.apiKeys.length > 0) {
      const { error } = await this.adminClient.from('api_keys').delete().in('id', this.createdIds.apiKeys)

      if (error) {
        errors.push(`Failed to delete API keys: ${error.message}`)
      }
    }

    // Delete memberships
    if (this.createdIds.memberships.length > 0) {
      const { error } = await this.adminClient.from('org_memberships').delete().in('id', this.createdIds.memberships)

      if (error) {
        errors.push(`Failed to delete memberships: ${error.message}`)
      }
    }

    // LEVEL 4: Delete base tables

    // Delete users
    if (this.createdIds.users.length > 0) {
      const { error } = await this.adminClient.from('users').delete().in('id', this.createdIds.users)

      if (error) {
        errors.push(`Failed to delete users: ${error.message}`)
      }
    }

    // Delete organizations
    if (this.createdIds.organizations.length > 0) {
      const { error } = await this.adminClient.from('organizations').delete().in('id', this.createdIds.organizations)

      if (error) {
        errors.push(`Failed to delete organizations: ${error.message}`)
      }
    }

    // Log any cleanup errors but don't throw
    if (errors.length > 0) {
      console.warn('\n⚠️  Cleanup warnings:', errors.join('\n'))
    }
  }
}
