import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { geocodeAddress } from '../mission-planner/src/services/GeocodeService'

// AZ-501 — mission-planner GeocodeService env-var hardening (mirrors AZ-499).
//
// Lives under tests/ rather than colocated under mission-planner/ for the
// same reason as mission_planner_weather.test.ts: mission-planner has no
// runner of its own; the suite Vitest config covers mission-planner/src.

type FetchMock = ReturnType<typeof vi.fn>

const okResponse = (lat: number, lng: number) =>
  new Response(
    JSON.stringify({
      status: 'OK',
      results: [{ geometry: { location: { lat, lng } } }],
    }),
    { status: 200 },
  )

describe('AZ-501 — mission-planner geocodeAddress (env vars + fail-soft)', () => {
  let fetchMock: FetchMock
  let warnSpy: ReturnType<typeof vi.spyOn>

  beforeEach(() => {
    fetchMock = vi.fn(async () => okResponse(50.45, 30.52))
    vi.spyOn(globalThis, 'fetch').mockImplementation(fetchMock)
    warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
  })

  afterEach(() => {
    vi.restoreAllMocks()
    vi.unstubAllEnvs()
  })

  it('AC-1: env-var resolved API key reaches the outgoing fetch URL', async () => {
    // Arrange
    vi.stubEnv('VITE_GOOGLE_GEOCODE_KEY', 'env-key-xyz')

    // Act
    const result = await geocodeAddress('Kyiv, Ukraine')

    // Assert
    expect(fetchMock).toHaveBeenCalledTimes(1)
    const url = String(fetchMock.mock.calls[0][0])
    expect(url).toContain('key=env-key-xyz')
    expect(url).toContain('address=Kyiv%2C%20Ukraine')
    expect(result).toEqual({ lat: 50.45, lng: 30.52 })
  })

  it('AC-3: returns null, issues no fetch, and warns when VITE_GOOGLE_GEOCODE_KEY is unset', async () => {
    // Arrange
    vi.stubEnv('VITE_GOOGLE_GEOCODE_KEY', '')

    // Act
    const result = await geocodeAddress('Kyiv, Ukraine')

    // Assert
    expect(result).toBeNull()
    expect(fetchMock).not.toHaveBeenCalled()
    expect(warnSpy).toHaveBeenCalledTimes(1)
    expect(String(warnSpy.mock.calls[0][0])).toContain('VITE_GOOGLE_GEOCODE_KEY')
  })

  it('AC-3: still returns null and does not throw when fetch rejects (network error fail-soft)', async () => {
    // Arrange
    vi.stubEnv('VITE_GOOGLE_GEOCODE_KEY', 'env-key-xyz')
    fetchMock.mockRejectedValueOnce(new Error('boom'))

    // Act
    const result = await geocodeAddress('Kyiv, Ukraine')

    // Assert
    expect(result).toBeNull()
  })

  it('returns null when the response status is non-OK (e.g. ZERO_RESULTS)', async () => {
    // Arrange
    vi.stubEnv('VITE_GOOGLE_GEOCODE_KEY', 'env-key-xyz')
    fetchMock.mockResolvedValueOnce(
      new Response(JSON.stringify({ status: 'ZERO_RESULTS', results: [] }), {
        status: 200,
      }),
    )

    // Act
    const result = await geocodeAddress('nowhere-place-12345')

    // Assert
    expect(result).toBeNull()
  })

  it('AC-4 (defense-in-depth): no live key string is hardcoded in the service module', async () => {
    // Assert: importing GeocodeService.ts must NOT bring along the previously
    // hardcoded literal. Done as a sanity assertion on the resolved URL when
    // no env var is present — ensures we cannot accidentally leak a fallback.
    vi.stubEnv('VITE_GOOGLE_GEOCODE_KEY', '')
    await geocodeAddress('test')
    expect(fetchMock).not.toHaveBeenCalled()
  })
})
