jest.mock('@/shared/solacenet/sdk', () => ({
	requireCapability: jest.fn().mockResolvedValue(undefined),
	checkCapability: jest.fn(),
	getCapabilityState: jest.fn(),
}));

import type { Request } from 'express';
import {
	gatewayRailsEnforcementEnabled,
	maybeRequireGatewayMicroservices,
	requireGatewayMicroservicesForWorker,
	resolveGatewayTenantId,
} from '@/core/gateway/rails/gateway-rails-enforcement';
import { requireCapability } from '@/shared/solacenet/sdk';
import { logger } from '@/infrastructure/monitoring/logger';
import { DbisError, ErrorCode } from '@/shared/types';

const mockRequireCapability = requireCapability as jest.MockedFunction<typeof requireCapability>;

describe('gateway-rails-enforcement', () => {
	const origEnforce = process.env.SOLACENET_GATEWAY_RAILS_ENFORCE;
	const origTenant = process.env.SOLACENET_DEFAULT_TENANT_ID;
	let logSpy: jest.SpyInstance;

	beforeAll(() => {
		logSpy = jest.spyOn(logger, 'info').mockImplementation(() => logger as never);
	});

	afterAll(() => {
		logSpy.mockRestore();
	});

	afterEach(() => {
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = origEnforce;
		process.env.SOLACENET_DEFAULT_TENANT_ID = origTenant;
	});

	it('is disabled when env unset or not 1/true', () => {
		delete process.env.SOLACENET_GATEWAY_RAILS_ENFORCE;
		expect(gatewayRailsEnforcementEnabled()).toBe(false);
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = '0';
		expect(gatewayRailsEnforcementEnabled()).toBe(false);
	});

	it('is enabled for 1 or true', () => {
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = '1';
		expect(gatewayRailsEnforcementEnabled()).toBe(true);
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = 'true';
		expect(gatewayRailsEnforcementEnabled()).toBe(true);
	});

	it('resolves tenant from body, header, env, then system', () => {
		const req = { body: { tenantId: 't-body' }, headers: {} } as Request;
		expect(resolveGatewayTenantId(req)).toBe('t-body');

		const req2 = { body: {}, headers: { 'x-tenant-id': 't-header' } } as unknown as Request;
		expect(resolveGatewayTenantId(req2)).toBe('t-header');

		delete process.env.SOLACENET_DEFAULT_TENANT_ID;
		const req3 = { body: {}, headers: {} } as Request;
		expect(resolveGatewayTenantId(req3)).toBe('system');

		process.env.SOLACENET_DEFAULT_TENANT_ID = 't-env';
		expect(resolveGatewayTenantId(req3)).toBe('t-env');
	});

	it('maybeRequireGatewayMicroservices calls requireCapability when enforcement on', async () => {
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = '1';
		mockRequireCapability.mockClear();
		const req = { body: { tenantId: 'acme' }, headers: {} } as Request;
		await maybeRequireGatewayMicroservices(req);
		expect(mockRequireCapability).toHaveBeenCalledWith(
			'gateway-microservices',
			expect.objectContaining({ tenantId: 'acme', capabilityId: 'gateway-microservices' }),
		);
	});

	it('maybeRequireGatewayMicroservices skips when enforcement off', async () => {
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = '0';
		mockRequireCapability.mockClear();
		await maybeRequireGatewayMicroservices({ body: {}, headers: {} } as Request);
		expect(mockRequireCapability).not.toHaveBeenCalled();
	});

	it('maybeRequireGatewayMicroservices logs deny and rethrows on DbisError', async () => {
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = '1';
		mockRequireCapability.mockRejectedValueOnce(new DbisError(ErrorCode.FORBIDDEN, 'denied'));
		const req = {
			body: { tenantId: 'acme' },
			headers: {},
			params: { adapterId: 'dbis.adapter.ktt-evidence' },
			path: '/rails/dbis.adapter.ktt-evidence/health',
			method: 'GET',
		} as unknown as Request;
		await expect(maybeRequireGatewayMicroservices(req)).rejects.toThrow(DbisError);
		expect(logSpy).toHaveBeenCalledWith(
			'Gateway rails enforcement audit',
			expect.objectContaining({
				decision: 'deny',
				tenantId: 'acme',
				adapterId: 'dbis.adapter.ktt-evidence',
			}),
		);
	});

	it('requireGatewayMicroservicesForWorker allows when capability ok', async () => {
		process.env.SOLACENET_GATEWAY_RAILS_ENFORCE = '1';
		mockRequireCapability.mockResolvedValueOnce(undefined);
		await requireGatewayMicroservicesForWorker({
			tenantId: 't1',
			ingressKind: 'mq',
			detail: 'queue:gateway.in',
		});
		expect(mockRequireCapability).toHaveBeenCalledWith(
			'gateway-microservices',
			expect.objectContaining({ tenantId: 't1', channel: 'mq' }),
		);
	});
});
