import { describe, expect, it, vi } from 'vitest';
import { GraphQLError } from 'graphql';
import { AdminContextProvider } from '../providers/admin-context.provider.js';
import type { TenantAwareDBClient } from '../../app-providers/tenant-db-client.js';
import type { AuthContextProvider } from '../../auth/providers/auth-context.provider.js';

function createProvider(options: {
  businessId: string | null;
  activeReadScopeBusinessIds?: string[];
  row?: Record<string, unknown>;
  rowsByOwnerId?: Record<string, Record<string, unknown>>;
}) {
  const query = vi.fn().mockImplementation((_statement, values) => {
    if (options.rowsByOwnerId) {
      const serializedValues = JSON.stringify(values ?? []);
      for (const [ownerId, row] of Object.entries(options.rowsByOwnerId)) {
        if (serializedValues.includes(ownerId)) {
          return Promise.resolve({ rows: [row], rowCount: 1 });
        }
      }
      return Promise.resolve({ rows: [], rowCount: 0 });
    }

    return Promise.resolve({
      rows: options.row ? [options.row] : [],
      rowCount: options.row ? 1 : 0,
    });
  });

  const db = {
    query,
  } as unknown as TenantAwareDBClient;

  const auth = {
    getAuthContext: vi.fn().mockResolvedValue(
      options.businessId
        ? {
            authType: 'jwt',
            tenant: { businessId: options.businessId },
            ...(options.activeReadScopeBusinessIds
              ? {
                  activeReadScope: {
                    businessIds: options.activeReadScopeBusinessIds,
                  },
                }
              : {}),
          }
        : null,
    ),
  } as unknown as AuthContextProvider;

  return {
    provider: new AdminContextProvider(auth, db),
    query,
    auth,
  };
}

describe('AdminContext DI Integration', () => {
  it('loads admin context through provider injection', async () => {
    const { provider, query, auth } = createProvider({
      businessId: 'owner-a',
      row: {
        owner_id: 'owner-a',
        default_local_currency: 'USD',
        default_fiat_currency_for_crypto_conversions: 'USD',
        date_established: null,
        initial_accounter_year: null,
      },
    });

    const context = await provider.getVerifiedAdminContext();

    expect(context.ownerId).toBe('owner-a');
    expect(context.defaultLocalCurrency).toBe('USD');
    expect(auth.getAuthContext).toHaveBeenCalledTimes(1);
    expect(query).toHaveBeenCalledTimes(1);
  });

  it('uses single-business active scope as owner selection', async () => {
    const primaryOwnerId = 'owner-primary';
    const scopedOwnerId = 'owner-scoped';
    const { provider } = createProvider({
      businessId: primaryOwnerId,
      activeReadScopeBusinessIds: [scopedOwnerId],
      rowsByOwnerId: {
        [scopedOwnerId]: {
          owner_id: scopedOwnerId,
          default_local_currency: 'USD',
          default_fiat_currency_for_crypto_conversions: 'USD',
          date_established: null,
          initial_accounter_year: null,
        },
      },
    });

    const context = await provider.getVerifiedAdminContext();
    expect(context.ownerId).toBe(scopedOwnerId);
  });

  it('prefers primary tenant business when it is inside multi-business scope', async () => {
    const primaryOwnerId = 'owner-primary';
    const { provider } = createProvider({
      businessId: primaryOwnerId,
      activeReadScopeBusinessIds: ['owner-scoped-a', primaryOwnerId, 'owner-scoped-b'],
      rowsByOwnerId: {
        [primaryOwnerId]: {
          owner_id: primaryOwnerId,
          default_local_currency: 'USD',
          default_fiat_currency_for_crypto_conversions: 'USD',
          date_established: null,
          initial_accounter_year: null,
        },
      },
    });

    const context = await provider.getVerifiedAdminContext();
    expect(context.ownerId).toBe(primaryOwnerId);
  });

  it('falls back to first scoped business when primary tenant business is outside scope', async () => {
    const primaryOwnerId = 'owner-primary';
    const firstScopedOwnerId = 'owner-scoped-a';
    const { provider } = createProvider({
      businessId: primaryOwnerId,
      activeReadScopeBusinessIds: [firstScopedOwnerId, 'owner-scoped-b'],
      rowsByOwnerId: {
        [firstScopedOwnerId]: {
          owner_id: firstScopedOwnerId,
          default_local_currency: 'USD',
          default_fiat_currency_for_crypto_conversions: 'USD',
          date_established: null,
          initial_accounter_year: null,
        },
      },
    });

    const context = await provider.getVerifiedAdminContext();
    expect(context.ownerId).toBe(firstScopedOwnerId);
  });

  it('isolates admin context between concurrent requests', async () => {
    const requestA = createProvider({
      businessId: 'owner-a',
      row: {
        owner_id: 'owner-a',
        default_local_currency: 'USD',
        default_fiat_currency_for_crypto_conversions: 'USD',
        date_established: null,
        initial_accounter_year: null,
      },
    });

    const requestB = createProvider({
      businessId: 'owner-b',
      row: {
        owner_id: 'owner-b',
        default_local_currency: 'EUR',
        default_fiat_currency_for_crypto_conversions: 'EUR',
        date_established: null,
        initial_accounter_year: null,
      },
    });

    const [contextA, contextB] = await Promise.all([
      requestA.provider.getVerifiedAdminContext(),
      requestB.provider.getVerifiedAdminContext(),
    ]);

    expect(contextA.ownerId).toBe('owner-a');
    expect(contextB.ownerId).toBe('owner-b');
    expect(requestA.query).toHaveBeenCalledTimes(1);
    expect(requestB.query).toHaveBeenCalledTimes(1);
  });

  it('caches admin context within a single request', async () => {
    const { provider, query } = createProvider({
      businessId: 'owner-a',
      row: {
        owner_id: 'owner-a',
        default_local_currency: 'USD',
        default_fiat_currency_for_crypto_conversions: 'USD',
        date_established: null,
        initial_accounter_year: null,
      },
    });

    const [first, second] = await Promise.all([
      provider.getVerifiedAdminContext(),
      provider.getVerifiedAdminContext(),
    ]);

    expect(first.ownerId).toBe('owner-a');
    expect(second.ownerId).toBe('owner-a');
    expect(query).toHaveBeenCalledTimes(1);
  });

  it('fails closed when auth context is unavailable (RLS guard path)', async () => {
    const { provider, query } = createProvider({
      businessId: null,
    });

    await expect(provider.getAdminContext()).rejects.toBeInstanceOf(GraphQLError);
    await expect(provider.getAdminContext()).rejects.toMatchObject({
      extensions: { code: 'UNAUTHENTICATED' },
    });
    expect(query).not.toHaveBeenCalled();
  });
});
