// #region Test.EnvironmentContext [C:3] [TYPE Module] [SEMANTICS test, environment, context, store, selector, filter]
// @BRIEF Unit tests for environment context store — initial state, refresh with auth, env selection, production detection, error handling.
// @RELATION DEPENDS_ON -> [EnvironmentContextStore]
// @TEST_EDGE: missing_field -> refresh with no auth token returns early without API call
// @TEST_EDGE: invalid_type -> environments list empty is handled gracefully (selectedEnvId = '')
// @TEST_EDGE: external_fail -> api.getEnvironmentsList rejects with 401 or generic error

import { describe, it, expect, beforeEach, vi } from 'vitest';

vi.mock('$lib/api', () => ({
	api: {
		getEnvironmentsList: vi.fn(),
	},
}));

const mockBrowser = vi.hoisted(() => ({ browser: true }));
vi.mock('$app/environment', () => mockBrowser);

// ── Test fixtures ─────────────────────────────────────────────────────

const mockEnvironments = [
	{ id: 'env-dev', name: 'Development', stage: 'dev' },
	{ id: 'env-staging', name: 'Staging', stage: 'staging' },
	{ id: 'env-prod', name: 'Production', stage: 'prod', is_production: true },
];

// ── Tests ─────────────────────────────────────────────────────────────

describe('environmentContext store', () => {
	beforeEach(() => {
		mockBrowser.browser = true;
		vi.resetModules();
		vi.clearAllMocks();
		localStorage.clear();
		// Set auth token so hasAuthToken() returns true by default
		localStorage.setItem('auth_token', 'test-token');
	});

	// ── Initial state ────────────────────────────────────────────────

	it('should have correct initial state', async () => {
		const { environmentContextStore } = await import('../environmentContext.svelte.js');

		const state = environmentContextStore.value;
		expect(state.environments).toEqual([]);
		expect(state.selectedEnvId).toBe('');
		expect(state.isLoading).toBe(false);
		expect(state.isLoaded).toBe(false);
		expect(state.error).toBeNull();
	});

	// ── Subscribe ────────────────────────────────────────────────────

	it('environmentContextStore.subscribe should call fn with current state', async () => {
		const { environmentContextStore } = await import('../environmentContext.svelte.js');

		const spy = vi.fn();
		const unsubscribe = environmentContextStore.subscribe(spy);
		expect(spy).toHaveBeenCalledTimes(1);
		expect(spy).toHaveBeenCalledWith(
			expect.objectContaining({
				environments: [],
				selectedEnvId: '',
				isLoading: false,
				isLoaded: false,
			}),
		);
		unsubscribe();
	});

	// ── refresh with auth ────────────────────────────────────────────

	it('refreshEnvironmentContext with auth token should call api and update state', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(api.getEnvironmentsList).toHaveBeenCalledTimes(1);
		expect(environmentContextStore.value.environments).toEqual(mockEnvironments);
		expect(environmentContextStore.value.isLoading).toBe(false);
		expect(environmentContextStore.value.isLoaded).toBe(true);
		expect(environmentContextStore.value.error).toBeNull();
	});

	it('refreshEnvironmentContext should select first env when no preferred or stored', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		// Remove any stored preference
		localStorage.removeItem('selected_env_id');

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(environmentContextStore.value.selectedEnvId).toBe('env-dev');
	});

	it('refreshEnvironmentContext should prefer stored env if valid', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		// Set a stored preference for staging
		localStorage.setItem('selected_env_id', 'env-staging');

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(environmentContextStore.value.selectedEnvId).toBe('env-staging');
	});

	it('refreshEnvironmentContext should use preferredEnvId if valid', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext('env-prod');

		expect(environmentContextStore.value.selectedEnvId).toBe('env-prod');
	});

	it('refreshEnvironmentContext should prefer preferredEnvId over stored', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		localStorage.setItem('selected_env_id', 'env-staging');

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext('env-prod');

		// preferredEnvId wins over stored
		expect(environmentContextStore.value.selectedEnvId).toBe('env-prod');
	});

	// ── refresh without auth ─────────────────────────────────────────

	it('refreshEnvironmentContext without auth token should return early', async () => {
		localStorage.removeItem('auth_token');

		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(api.getEnvironmentsList).not.toHaveBeenCalled();
		expect(environmentContextStore.value.isLoading).toBe(false);
		expect(environmentContextStore.value.isLoaded).toBe(false);
	});

	// ── refresh 401 error ────────────────────────────────────────────

	it('refreshEnvironmentContext should handle 401 error gracefully', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockRejectedValue({ status: 401, message: 'Unauthorized' });

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(environmentContextStore.value.isLoading).toBe(false);
		expect(environmentContextStore.value.isLoaded).toBe(false);
		expect(environmentContextStore.value.error).toBeNull();
		// API should have been called (but returned 401)
		expect(api.getEnvironmentsList).toHaveBeenCalledTimes(1);
	});

	// ── refresh generic error ────────────────────────────────────────

	it('refreshEnvironmentContext should handle generic error and set error message', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockRejectedValue({
			status: 500,
			message: 'Server error',
		});

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(environmentContextStore.value.isLoading).toBe(false);
		expect(environmentContextStore.value.isLoaded).toBe(true);
		expect(environmentContextStore.value.error).toBe('Server error');
		expect(environmentContextStore.value.environments).toEqual([]);
	});

	// ── setSelectedEnvironment ───────────────────────────────────────

	it('setSelectedEnvironment should update selected env', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const { environmentContextStore, refreshEnvironmentContext, setSelectedEnvironment } =
			await import('../environmentContext.svelte.js');

		await refreshEnvironmentContext();
		expect(environmentContextStore.value.selectedEnvId).toBe('env-dev');

		setSelectedEnvironment('env-staging');

		expect(environmentContextStore.value.selectedEnvId).toBe('env-staging');
	});

	it('setSelectedEnvironment with unknown env id should set empty', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const { environmentContextStore, refreshEnvironmentContext, setSelectedEnvironment } =
			await import('../environmentContext.svelte.js');

		await refreshEnvironmentContext();

		setSelectedEnvironment('env-nonexistent');

		expect(environmentContextStore.value.selectedEnvId).toBe('');
	});

	// ── selectedEnvironmentStore ─────────────────────────────────────

	it('selectedEnvironmentStore should return correct env object', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const {
			environmentContextStore,
			selectedEnvironmentStore,
			refreshEnvironmentContext,
			setSelectedEnvironment,
		} = await import('../environmentContext.svelte.js');

		await refreshEnvironmentContext();

		// Initially first env (Development)
		expect(selectedEnvironmentStore.value?.id).toBe('env-dev');
		expect(selectedEnvironmentStore.value?.name).toBe('Development');

		// Switch to Production
		setSelectedEnvironment('env-prod');
		expect(selectedEnvironmentStore.value?.id).toBe('env-prod');
		expect(selectedEnvironmentStore.value?.name).toBe('Production');
	});

	it('selectedEnvironmentStore should return null when no env selected', async () => {
		const { selectedEnvironmentStore } = await import('../environmentContext.svelte.js');

		expect(selectedEnvironmentStore.value).toBeNull();
	});

	it('selectedEnvironmentStore.subscribe should call fn with current env', async () => {
		const { selectedEnvironmentStore } = await import('../environmentContext.svelte.js');

		const spy = vi.fn();
		const unsubscribe = selectedEnvironmentStore.subscribe(spy);
		expect(spy).toHaveBeenCalledTimes(1);
		expect(spy).toHaveBeenCalledWith(null);
		unsubscribe();
	});

	// ── isProductionContextStore ─────────────────────────────────────

	it('isProductionContextStore should detect production env by stage', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const {
			isProductionContextStore,
			refreshEnvironmentContext,
			setSelectedEnvironment,
		} = await import('../environmentContext.svelte.js');

		await refreshEnvironmentContext();

		// Development — not production
		setSelectedEnvironment('env-dev');
		expect(isProductionContextStore.value).toBe(false);

		// Production by stage 'prod'
		setSelectedEnvironment('env-prod');
		expect(isProductionContextStore.value).toBe(true);
	});

	it('isProductionContextStore should detect production by is_production flag', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		const envsWithProdFlag = [
			{ id: 'env-1', name: 'Custom', stage: 'custom', is_production: true },
			{ id: 'env-2', name: 'Sandbox', stage: 'sandbox', is_production: false },
		];
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(envsWithProdFlag);

		const {
			isProductionContextStore,
			refreshEnvironmentContext,
			setSelectedEnvironment,
		} = await import('../environmentContext.svelte.js');

		await refreshEnvironmentContext();

		// Custom env with is_production=true (but stage is 'custom', not 'prod')
		setSelectedEnvironment('env-1');
		expect(isProductionContextStore.value).toBe(true);

		// Sandbox with is_production=false
		setSelectedEnvironment('env-2');
		expect(isProductionContextStore.value).toBe(false);
	});

	it('isProductionContextStore.subscribe should call fn with current value', async () => {
		const { isProductionContextStore } = await import('../environmentContext.svelte.js');

		const spy = vi.fn();
		const unsubscribe = isProductionContextStore.subscribe(spy);
		expect(spy).toHaveBeenCalledTimes(1);
		expect(spy).toHaveBeenCalledWith(false);
		unsubscribe();
	});

	// ── initializeEnvironmentContext ─────────────────────────────────

	it('initializeEnvironmentContext should call refresh if not yet loaded', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const {
			environmentContextStore,
			initializeEnvironmentContext,
		} = await import('../environmentContext.svelte.js');

		expect(environmentContextStore.value.isLoaded).toBe(false);

		await initializeEnvironmentContext();

		expect(api.getEnvironmentsList).toHaveBeenCalledTimes(1);
		expect(environmentContextStore.value.isLoaded).toBe(true);
	});

	it('initializeEnvironmentContext should not double-fetch if already loaded', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const {
			environmentContextStore,
			initializeEnvironmentContext,
		} = await import('../environmentContext.svelte.js');

		await initializeEnvironmentContext();
		expect(environmentContextStore.value.isLoaded).toBe(true);

		// Clear the mock call count so we can verify no additional calls
		vi.mocked(api.getEnvironmentsList).mockClear();

		await initializeEnvironmentContext();

		expect(api.getEnvironmentsList).not.toHaveBeenCalled();
	});

	// ── Error boundary: empty env list ───────────────────────────────

	it('refreshEnvironmentContext with empty array should set selectedEnvId empty', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue([]);

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(environmentContextStore.value.environments).toEqual([]);
		expect(environmentContextStore.value.selectedEnvId).toBe('');
		expect(environmentContextStore.value.isLoaded).toBe(true);
	});

	// ── setSelectedEnvironment with empty envId → triggers localStorage.removeItem ──

	it('setSelectedEnvironment with empty string should clear stored env', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(mockEnvironments);

		const { environmentContextStore, refreshEnvironmentContext, setSelectedEnvironment } =
			await import('../environmentContext.svelte.js');

		await refreshEnvironmentContext();
		expect(environmentContextStore.value.selectedEnvId).toBe('env-dev');

		setSelectedEnvironment('');

		expect(environmentContextStore.value.selectedEnvId).toBe('');
		expect(localStorage.getItem('selected_env_id')).toBeNull();
	});

	// ── resolveSelectedEnvId with non-array environments ──────────────

	it('refreshEnvironmentContext should handle non-array API response gracefully', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();

		// The API returns a string instead of array — this causes issues in derived
		// Since the error propagates through the notification chain, we just verify
		// the store doesn't crash and isLoading is reset
		const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
		vi.mocked(api.getEnvironmentsList).mockResolvedValue('not-an-array' as any);

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		// The store may have an error from the derived throwing, but isLoading is reset
		expect(environmentContextStore.value.isLoading).toBe(false);

		consoleSpy.mockRestore();
	});

	// ── initializeEnvironmentContext should not double-fetch when loading ──

	it('initializeEnvironmentContext should not double-fetch if already loading', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		let resolvePromise!: (v: unknown) => void;
		vi.mocked(api.getEnvironmentsList).mockReturnValue(new Promise((r) => { resolvePromise = r; }));

		const { initializeEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		// Start loading by calling refresh (initialize won't start because isLoading=false initially)
		// Actually initialize checks _contextState.isLoading || _contextState.isLoaded
		// Let's call initialize twice simultaneously
		const promise1 = initializeEnvironmentContext();
		vi.mocked(api.getEnvironmentsList).mockClear();
		const promise2 = initializeEnvironmentContext();

		resolvePromise(mockEnvironments);
		await promise1;
		await promise2;

		// Only the first call should have triggered API
		expect(api.getEnvironmentsList).not.toHaveBeenCalled();
	});

	// ── hasAuthToken with missing token ───────────────────────────────

	it('refreshEnvironmentContext should handle no environments when auth token exists', async () => {
		localStorage.setItem('auth_token', 'test-token');

		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockResolvedValue(undefined);

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		// No environments returned
		expect(environmentContextStore.value.isLoaded).toBe(true);
	});

	// ── error handler with status 401 (no message) ────────────────────

	it('refreshEnvironmentContext should handle 401 error without message', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockRejectedValue({ status: 401 });

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(environmentContextStore.value.isLoading).toBe(false);
		expect(environmentContextStore.value.isLoaded).toBe(false);
		expect(environmentContextStore.value.error).toBeNull();
	});

	// ── refresh with generic error (no message field) ─────────────────

	it('refreshEnvironmentContext should handle error without message field', async () => {
		const { api } = await import('$lib/api');
		vi.mocked(api.getEnvironmentsList).mockClear();
		vi.mocked(api.getEnvironmentsList).mockRejectedValue({ status: 500 });

		const { environmentContextStore, refreshEnvironmentContext } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		expect(environmentContextStore.value.isLoaded).toBe(true);
		expect(environmentContextStore.value.error).toBe('Failed to load environments');
	});

	// ── !browser guard coverage ───────────────────────────────────────

	it('hasAuthToken should return false when not in browser', async () => {
		mockBrowser.browser = false;

		const { refreshEnvironmentContext, environmentContextStore } = await import(
			'../environmentContext.svelte.js'
		);

		await refreshEnvironmentContext();

		// With no auth token, refresh should not call API
		const { api } = await import('$lib/api');
		expect(api.getEnvironmentsList).not.toHaveBeenCalled();
		expect(environmentContextStore.value.isLoading).toBe(false);
		expect(environmentContextStore.value.isLoaded).toBe(false);
	});

	// ── persistSelectedEnvId !browser guard ──────────────────────────

	it('persistSelectedEnvId should handle !browser guard via setSelectedEnvironment', async () => {
		mockBrowser.browser = false;

		const { setSelectedEnvironment } = await import('../environmentContext.svelte.js');

		// persistSelectedEnvId checks `if (!browser) return;` at line 87.
		// This should be reachable via setSelectedEnvironment → applySelectedEnvId.
		expect(() => setSelectedEnvironment('some-env')).not.toThrow();
	});

	// ── getStoredSelectedEnvId !browser guard (via deferred promise) ──

	it('covers getStoredSelectedEnvId !browser guard with deferred API promise', async () => {
		mockBrowser.browser = true;
		localStorage.setItem('auth_token', 'test-token');

		const { api } = await import('$lib/api');
		const { refreshEnvironmentContext, environmentContextStore } = await import(
			'../environmentContext.svelte.js'
		);

		// Create a deferred promise — refreshEnvironmentContext awaits it on line 130
		let resolveApi!: (v: unknown) => void;
		vi.mocked(api.getEnvironmentsList).mockReturnValue(
			new Promise((r) => { resolveApi = r; }),
		);

		// Start refresh — hasAuthToken passes (browser=true), then awaits API call
		const promise = refreshEnvironmentContext();

		// Flip browser to false while refresh is awaiting the API call
		mockBrowser.browser = false;

		// Resolve the API — resolveSelectedEnvId → getStoredSelectedEnvId sees browser=false → returns ''
		resolveApi(mockEnvironments);
		await promise;

		// getStoredSelectedEnvId returned '' → falls through to environments[0].id ('env-dev')
		expect(environmentContextStore.value.selectedEnvId).toBe('env-dev');
		expect(environmentContextStore.value.isLoading).toBe(false);
	});
});
// #endregion Test.EnvironmentContext
