/**
 * Integration test for GET /projects/log (W3 quick-wins d).
 *
 * Starts a real HTTP server, then asserts the recent-commit log route against a
 * real temp git repo (3 commits): 200 + structured GitLogResult; missing path →
 * 400; a non-git temp dir → 404 (isValidGitDir three-prong); ?n clamped.
 */

import fs from 'node:fs/promises'
import net from 'node:net'
import os from 'node:os'
import path from 'node:path'
import { execFile } from 'node:child_process'
import { promisify } from 'node:util'

import { afterAll, beforeAll, describe, expect, it } from 'vitest'

import { loadConfig } from '../../src/config.js'
import { startServer } from '../../src/server.js'
import type { GitLogResult } from '../../src/types.js'

const execFileAsync = promisify(execFile)

function getFreePort(): Promise<number> {
  return new Promise((resolve, reject) => {
    const srv = net.createServer()
    srv.listen(0, '127.0.0.1', () => {
      const addr = srv.address()
      if (addr === null || typeof addr === 'string') {
        srv.close()
        reject(new Error('unexpected address type'))
        return
      }
      const port = addr.port
      srv.close(() => resolve(port))
    })
    srv.on('error', reject)
  })
}

async function git(cwd: string, ...args: string[]): Promise<void> {
  await execFileAsync('git', args, { cwd })
}

async function gitAvailable(): Promise<boolean> {
  try {
    await execFileAsync('git', ['--version'])
    return true
  } catch {
    return false
  }
}

describe('GET /projects/log — integration', () => {
  let port: number
  let tmpRoot: string
  let repoPath: string
  let plainPath: string
  let serverHandle: { close(): Promise<void> }
  let haveGit = false

  beforeAll(async () => {
    haveGit = await gitAvailable()
    port = await getFreePort()

    tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'webterm-log-test-'))
    repoPath = path.join(tmpRoot, 'repo')
    plainPath = path.join(tmpRoot, 'plain')
    await fs.mkdir(plainPath, { recursive: true })

    if (haveGit) {
      await fs.mkdir(repoPath, { recursive: true })
      await git(repoPath, 'init', '-q', '-b', 'main')
      await git(repoPath, 'config', 'user.email', 't@t.local')
      await git(repoPath, 'config', 'user.name', 'tester')
      await git(repoPath, 'config', 'commit.gpgsign', 'false')
      for (const [file, msg] of [['a.txt', 'first'], ['b.txt', 'second'], ['c.txt', 'third']]) {
        await fs.writeFile(path.join(repoPath, file), `${file}\n`)
        await git(repoPath, 'add', '.')
        await git(repoPath, 'commit', '-q', '-m', msg)
      }
    }

    const cfg = loadConfig({
      PORT: String(port),
      BIND_HOST: '127.0.0.1',
      SHELL_PATH: process.env['SHELL'] ?? '/bin/zsh',
      ALLOWED_ORIGINS: `http://127.0.0.1:${port}`,
      USE_TMUX: '0',
      IDLE_TTL: '86400',
    })
    serverHandle = startServer(cfg)
    await new Promise<void>((r) => setTimeout(r, 100))
  })

  afterAll(async () => {
    await serverHandle.close()
    await fs.rm(tmpRoot, { recursive: true, force: true })
  })

  it('returns 400 when path is missing', async () => {
    const res = await fetch(`http://127.0.0.1:${port}/projects/log`)
    expect(res.status).toBe(400)
  })

  it('returns 404 for a non-git directory (isValidGitDir three-prong)', async () => {
    const res = await fetch(`http://127.0.0.1:${port}/projects/log?path=${encodeURIComponent(plainPath)}`)
    expect(res.status).toBe(404)
  })

  it('returns 200 with the recent commits newest-first', async () => {
    if (!haveGit) return
    const res = await fetch(`http://127.0.0.1:${port}/projects/log?path=${encodeURIComponent(repoPath)}`)
    expect(res.status).toBe(200)
    const body = (await res.json()) as GitLogResult
    expect(body.commits.map((c) => c.subject)).toEqual(['third', 'second', 'first'])
  })

  it('clamps ?n and flags truncated', async () => {
    if (!haveGit) return
    const res = await fetch(
      `http://127.0.0.1:${port}/projects/log?path=${encodeURIComponent(repoPath)}&n=2`,
    )
    const body = (await res.json()) as GitLogResult
    expect(body.commits).toHaveLength(2)
    expect(body.truncated).toBe(true)

    // A huge n must be clamped to ≤ 50 (never explodes) — here only 3 commits exist.
    const res2 = await fetch(
      `http://127.0.0.1:${port}/projects/log?path=${encodeURIComponent(repoPath)}&n=999`,
    )
    const body2 = (await res2.json()) as GitLogResult
    expect(body2.commits.length).toBeLessThanOrEqual(50)
    expect(body2.commits).toHaveLength(3)
  })
})
