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

vi.mock('@/lib/prisma', () => ({
  prisma: {
    product: { findFirst: vi.fn() },
    idea: {
      findFirst: vi.fn(),
      findMany: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
    },
    ideaLog: { findMany: vi.fn() },
    $transaction: vi.fn(),
  },
}))
vi.mock('@/lib/api-auth', () => ({
  authenticateApiRequest: vi.fn(),
}))
vi.mock('@/lib/idea-code-server', () => ({
  nextIdeaCode: vi.fn().mockResolvedValue('IDEA-001'),
}))

import { prisma } from '@/lib/prisma'
import { authenticateApiRequest } from '@/lib/api-auth'
import { GET as getIdeas, POST as postIdea } from '@/app/api/ideas/route'
import { GET as getIdea, PATCH as patchIdea } from '@/app/api/ideas/[id]/route'

type M = {
  product: { findFirst: ReturnType<typeof vi.fn> }
  idea: { findFirst: ReturnType<typeof vi.fn>; findMany: ReturnType<typeof vi.fn>; create: ReturnType<typeof vi.fn>; update: ReturnType<typeof vi.fn> }
  ideaLog: { findMany: ReturnType<typeof vi.fn> }
  $transaction: ReturnType<typeof vi.fn>
}
const m = prisma as unknown as M
const mockAuth = authenticateApiRequest as ReturnType<typeof vi.fn>

const NOW = new Date('2026-05-04T19:00:00Z')

const IDEA_ROW = {
  id: 'idea-1',
  user_id: 'user-1',
  code: 'IDEA-001',
  title: 'Plant-watering reminder',
  description: null,
  status: 'DRAFT' as const,
  product_id: null,
  product: null,
  pbi: null,
  pbi_id: null,
  archived: false,
  grill_md: null,
  plan_md: null,
  created_at: NOW,
  updated_at: NOW,
}

function makeRequest(method: 'GET' | 'POST' | 'PATCH', url: string, body?: unknown): Request {
  return new Request(`http://localhost${url}`, {
    method,
    headers: {
      Authorization: 'Bearer test-token',
      'Content-Type': 'application/json',
    },
    body: body !== undefined ? JSON.stringify(body) : undefined,
  })
}

beforeEach(() => {
  vi.clearAllMocks()
  mockAuth.mockResolvedValue({ userId: 'user-1', isDemo: false })
  m.$transaction.mockImplementation(async (arg: unknown) => {
    if (typeof arg === 'function') return (arg as (tx: unknown) => unknown)(m)
    return arg
  })
})

describe('GET /api/ideas', () => {
  it('returns user ideas (DTO shape)', async () => {
    m.idea.findMany.mockResolvedValueOnce([IDEA_ROW])
    const res = await getIdeas(makeRequest('GET', '/api/ideas'))
    expect(res.status).toBe(200)
    const body = await res.json()
    expect(body.ideas).toHaveLength(1)
    expect(body.ideas[0]).toMatchObject({
      id: 'idea-1',
      code: 'IDEA-001',
      status: 'draft',
      has_grill_md: false,
    })
  })

  it('rejects unauthenticated', async () => {
    mockAuth.mockResolvedValueOnce({ error: 'Unauthorized', status: 401 })
    const res = await getIdeas(makeRequest('GET', '/api/ideas'))
    expect(res.status).toBe(401)
  })

  it('filters by archived=false param', async () => {
    m.idea.findMany.mockResolvedValueOnce([])
    await getIdeas(makeRequest('GET', '/api/ideas?archived=false'))
    expect(m.idea.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ archived: false, user_id: 'user-1' }),
      }),
    )
  })
})

describe('POST /api/ideas', () => {
  it('creates idea and returns 201', async () => {
    m.idea.create.mockResolvedValueOnce(IDEA_ROW)
    const res = await postIdea(makeRequest('POST', '/api/ideas', { title: 'Plant-watering reminder' }))
    expect(res.status).toBe(201)
    const body = await res.json()
    expect(body.idea).toMatchObject({ id: 'idea-1', code: 'IDEA-001', status: 'draft' })
  })

  it('rejects demo with 403', async () => {
    mockAuth.mockResolvedValueOnce({ userId: 'demo-1', isDemo: true })
    const res = await postIdea(makeRequest('POST', '/api/ideas', { title: 'x' }))
    expect(res.status).toBe(403)
  })

  it('rejects empty title with 422', async () => {
    const res = await postIdea(makeRequest('POST', '/api/ideas', { title: '' }))
    expect(res.status).toBe(422)
  })

  it('rejects malformed JSON with 400', async () => {
    const req = new Request('http://localhost/api/ideas', {
      method: 'POST',
      headers: { Authorization: 'Bearer test', 'Content-Type': 'application/json' },
      body: 'not-json',
    })
    const res = await postIdea(req)
    expect(res.status).toBe(400)
  })

  it('returns 404 when product_id refers to a foreign product', async () => {
    m.product.findFirst.mockResolvedValueOnce(null)
    const res = await postIdea(
      makeRequest('POST', '/api/ideas', {
        title: 'x',
        product_id: 'cmohrysyj0000rd17clnjy4tc',
      }),
    )
    expect(res.status).toBe(404)
  })
})

describe('GET /api/ideas/[id]', () => {
  it('returns idea + logs', async () => {
    m.idea.findFirst.mockResolvedValueOnce(IDEA_ROW)
    m.ideaLog.findMany.mockResolvedValueOnce([
      { id: 'l-1', type: 'NOTE', content: 'x', metadata: null, created_at: NOW },
    ])
    const ctx = { params: Promise.resolve({ id: 'idea-1' }) }
    const res = await getIdea(makeRequest('GET', '/api/ideas/idea-1'), ctx)
    expect(res.status).toBe(200)
    const body = await res.json()
    expect(body.idea).toMatchObject({ id: 'idea-1' })
    expect(body.logs).toHaveLength(1)
  })

  it('returns 404 (not 403) for foreign user — anti-enumeration', async () => {
    m.idea.findFirst.mockResolvedValueOnce(null)
    const ctx = { params: Promise.resolve({ id: 'idea-1' }) }
    const res = await getIdea(makeRequest('GET', '/api/ideas/idea-1'), ctx)
    expect(res.status).toBe(404)
  })
})

describe('PATCH /api/ideas/[id]', () => {
  const ctx = { params: Promise.resolve({ id: 'idea-1' }) }

  it('updates editable idea', async () => {
    m.idea.findFirst.mockResolvedValueOnce({ id: 'idea-1', status: 'DRAFT' })
    m.idea.update.mockResolvedValueOnce({ ...IDEA_ROW, title: 'Updated' })
    const res = await patchIdea(makeRequest('PATCH', '/api/ideas/idea-1', { title: 'Updated' }), ctx)
    expect(res.status).toBe(200)
  })

  it('blocks demo with 403', async () => {
    mockAuth.mockResolvedValueOnce({ userId: 'demo-1', isDemo: true })
    const res = await patchIdea(makeRequest('PATCH', '/api/ideas/idea-1', { title: 'x' }), ctx)
    expect(res.status).toBe(403)
  })

  it('blocks update on PLANNED with 422', async () => {
    m.idea.findFirst.mockResolvedValueOnce({ id: 'idea-1', status: 'PLANNED' })
    const res = await patchIdea(makeRequest('PATCH', '/api/ideas/idea-1', { title: 'x' }), ctx)
    expect(res.status).toBe(422)
  })
})
