import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { QueryClient } from '@tanstack/vue-query'
import { createPinia, setActivePinia } from 'pinia'
import {
  applyAgentFileWrite,
  fetchAgentActivity,
  fetchAgentDetail,
  fetchAgentFile,
  fetchAgentFiles,
  fetchAgentSummary,
  type AgentFileCollectionDto,
  type AgentFileDto,
  type AgentFileWriteDto,
} from '../src/api/agentDetail'
import {
  fetchDoc,
  fetchDocs,
  fetchMemoryFile,
  fetchMemoryFiles,
  fetchMemorySearch,
} from '../src/api/knowledge'
import { fetchIncident, fetchIncidents } from '../src/api/incidents'
import { queryKeys } from '../src/api/queryClient'
import { fetchSecurityStatus } from '../src/api/security'
import { useAuthStore } from '../src/stores/auth'

function requestUrl(input: RequestInfo | URL): URL {
  const raw = input instanceof Request ? input.url : String(input)
  return new URL(raw, 'http://localhost')
}

function jsonResponse(value: unknown) {
  return new Response(JSON.stringify(value), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  })
}

const file: AgentFileDto = {
  agentId: 'iris',
  name: 'SOUL.md',
  missing: false,
  size: 42,
  updatedAt: '2026-07-31T10:00:00.000Z',
  content: '# Iris',
  contentHash: 'hash-1',
  checkedAt: '2026-07-31T10:00:01.000Z',
}

beforeEach(() => {
  setActivePinia(createPinia())
  const auth = useAuthStore()
  auth.initialized = true
  auth.applySession({
    accessToken: 'server-state-token',
    expiresAt: '2026-07-31T12:00:00.000Z',
    user: {
      id: 'bao',
      email: 'bao@nexus.local',
      displayName: 'Bao',
      role: 'owner',
    },
  })
})

afterEach(() => {
  vi.unstubAllGlobals()
})

describe('remaining server-state fetchers', () => {
  it('loads agent detail, activity, summary and generated OpenClaw file contracts', async () => {
    const responses = new Map<string, unknown>([
      ['/api/v1/agents/iris', {
        id: 'iris',
        name: 'Iris',
        role: 'orchestrator',
        model: 'gpt-5',
        status: 'Online',
      }],
      ['/api/v1/agents/iris/activity', [{
        id: 1,
        type: 'task',
        message: 'Delegated work',
        at: '2026-07-31T09:00:00.000Z',
        source: 'openclaw',
      }]],
      ['/api/v1/agents/iris/summary', {
        now: { text: 'Working', source: 'openclaw' },
        today: { text: 'Completed review', source: 'openclaw' },
        generatedAt: '2026-07-31T10:00:00.000Z',
      }],
      ['/api/v1/openclaw/agents/iris/files', {
        agentId: 'iris',
        files: [{
          name: file.name,
          missing: file.missing,
          size: file.size,
          updatedAt: file.updatedAt,
          contentHash: file.contentHash,
        }],
        checkedAt: file.checkedAt,
      }],
      ['/api/v1/openclaw/agents/iris/files/SOUL.md', file],
    ])
    const fetchMock = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => {
      const value = responses.get(requestUrl(input).pathname)
      return value === undefined
        ? new Response(null, { status: 404 })
        : jsonResponse(value)
    })
    vi.stubGlobal('fetch', fetchMock)

    await expect(fetchAgentDetail('iris')).resolves.toMatchObject({ id: 'iris' })
    await expect(fetchAgentActivity('iris')).resolves.toHaveLength(1)
    await expect(fetchAgentSummary('iris')).resolves.toMatchObject({
      now: { text: 'Working' },
    })
    await expect(fetchAgentFiles('iris')).resolves.toMatchObject({
      files: [{ name: 'SOUL.md' }],
    })
    await expect(fetchAgentFile('iris', 'SOUL.md')).resolves.toEqual(file)

    expect(fetchMock).toHaveBeenCalledTimes(5)
    for (const [, init] of fetchMock.mock.calls) {
      expect(new Headers(init?.headers).get('Authorization'))
        .toBe('Bearer server-state-token')
    }
  })

  it('loads docs, memory, incidents and security through authenticated fetchers', async () => {
    const fetchMock = vi.fn(async (input: RequestInfo | URL) => {
      const url = requestUrl(input)
      if (url.pathname === '/api/v1/docs') return jsonResponse([])
      if (url.pathname === '/api/v1/docs/runbook%2Fdeploy.md') {
        return jsonResponse({ name: 'deploy.md', path: 'runbook/deploy.md', content: '# Deploy' })
      }
      if (url.pathname === '/api/v1/memory') return jsonResponse([])
      if (url.pathname === '/api/v1/memory/2026-07-31.md') {
        return jsonResponse({ name: '2026-07-31.md', content: 'Memory' })
      }
      if (url.pathname === '/api/v1/memory/search') {
        return jsonResponse([{ name: '2026-07-31.md', excerpt: 'Mission control' }])
      }
      if (url.pathname === '/api/v1/incidents') return jsonResponse([])
      if (url.pathname === '/api/v1/incidents/gateway.md') {
        return jsonResponse({ name: 'gateway.md', title: 'Gateway', content: '# Gateway' })
      }
      if (url.pathname === '/api/v1/security/status') {
        return jsonResponse({
          authMethod: 'JWT',
          tokenConfig: {
            issuer: 'nexus',
            audience: 'nexus-web',
            refreshTokenDays: 7,
            accessTokenMinutes: 15,
          },
          rateLimit: 'enabled',
          passwordPolicy: 'strong',
          cookieConfig: { httpOnly: true, secure: true, sameSite: 'Strict' },
          twoFactorEnabled: false,
          passkeyEnabled: false,
        })
      }
      return new Response(null, { status: 404 })
    })
    vi.stubGlobal('fetch', fetchMock)

    await expect(fetchDocs()).resolves.toEqual([])
    await expect(fetchDoc('runbook/deploy.md')).resolves.toMatchObject({ name: 'deploy.md' })
    await expect(fetchMemoryFiles()).resolves.toEqual([])
    await expect(fetchMemoryFile('2026-07-31.md')).resolves.toMatchObject({
      content: 'Memory',
    })
    await expect(fetchMemorySearch('mission control')).resolves.toHaveLength(1)
    await expect(fetchIncidents()).resolves.toEqual([])
    await expect(fetchIncident('gateway.md')).resolves.toMatchObject({ title: 'Gateway' })
    await expect(fetchSecurityStatus()).resolves.toMatchObject({ authMethod: 'JWT' })

    const urls = fetchMock.mock.calls.map(([input]) => requestUrl(input))
    expect(urls.find(url => url.pathname === '/api/v1/memory/search')
      ?.searchParams.get('q')).toBe('mission control')
  })
})

describe('agent file cache synchronization', () => {
  it('patches and invalidates only the written file and its collection', async () => {
    const client = new QueryClient()
    const collection: AgentFileCollectionDto = {
      agentId: 'iris',
      files: [{
        name: file.name,
        missing: true,
        size: null,
        updatedAt: null,
        contentHash: null,
      }],
      checkedAt: '2026-07-31T09:00:00.000Z',
    }
    const previousFile: AgentFileDto = {
      ...file,
      missing: true,
      size: null,
      updatedAt: null,
      content: null,
      contentHash: 'missing',
      checkedAt: collection.checkedAt,
    }
    const writeResult: AgentFileWriteDto = {
      ok: true,
      state: 'saved',
      message: 'Saved and verified.',
      file,
      verified: true,
      idempotencyKey: 'idem-1',
      correlationId: 'corr-1',
      completedAt: file.checkedAt,
    }
    client.setQueryData(queryKeys.agentFiles('iris'), collection)
    client.setQueryData(queryKeys.agentFile('iris', 'SOUL.md'), previousFile)
    client.setQueryData(queryKeys.agentFile('iris', 'TOOLS.md'), { marker: 'other' })

    await applyAgentFileWrite(client, 'iris', writeResult)

    expect(client.getQueryData(queryKeys.agentFile('iris', 'SOUL.md'))).toEqual(file)
    expect(client.getQueryData<AgentFileCollectionDto>(queryKeys.agentFiles('iris')))
      .toMatchObject({
        checkedAt: file.checkedAt,
        files: [{ name: 'SOUL.md', missing: false, contentHash: 'hash-1' }],
      })
    expect(client.getQueryState(queryKeys.agentFiles('iris'))?.isInvalidated).toBe(true)
    expect(client.getQueryState(queryKeys.agentFile('iris', 'SOUL.md'))?.isInvalidated)
      .toBe(true)
    expect(client.getQueryState(queryKeys.agentFile('iris', 'TOOLS.md'))?.isInvalidated)
      .toBe(false)
  })
})
