// OEE v1 route tests — F-007 / Batch 6.
//
// Verifies:
//   - GET uses `calendar_week` column (not `week_number`) for gte/lte filters
//   - POST maps request body `week_number` → DB column `calendar_week` on upsert
//   - POST upserts on the project-scoped conflict target (idempotent, no duplicates)
//   - POST returns 400 when required fields are missing
//   - GET/POST return 401 when no auth claims are present
//
// Strategy: mock @/lib/supabase/server so the module never touches a real DB.
// We capture the arguments passed to `.gte()`, `.lte()`, `.insert()`, and `.upsert()`.

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

// ─── Chainable query builder mock ────────────────────────────────────────────

interface RecordedFilter { method: string; col: string; val: unknown }
interface RecordedInsert { table: string; row: unknown }
interface RecordedUpsert { table: string; row: unknown; opts: unknown }

function makeSupabaseMock(opts: {
  authenticated?: boolean
  insertResult?: { data: unknown; error: null | { message: string } }
} = {}) {
  const authenticated = opts.authenticated ?? true
  const insertResult  = opts.insertResult ?? { data: { id: 'rec-1' }, error: null }

  const recordedFilters: RecordedFilter[] = []
  const recordedInserts: RecordedInsert[] = []
  const recordedUpserts: RecordedUpsert[] = []

  function makeQueryChain(table: string) {
    const chain: Record<string, unknown> = {}

    // Pagination / ordering
    chain.order   = vi.fn(() => chain)
    chain.range   = vi.fn(() => chain)

    // Filters — record which column + value is used so tests can assert
    chain.ilike = vi.fn((col: string, val: unknown) => {
      recordedFilters.push({ method: 'ilike', col, val })
      return chain
    })
    chain.eq = vi.fn((col: string, val: unknown) => {
      recordedFilters.push({ method: 'eq', col, val })
      return chain
    })
    chain.gte = vi.fn((col: string, val: unknown) => {
      recordedFilters.push({ method: 'gte', col, val })
      return chain
    })
    chain.lte = vi.fn((col: string, val: unknown) => {
      recordedFilters.push({ method: 'lte', col, val })
      return chain
    })

    // select with count option — return empty list
    chain.select = vi.fn(() => chain)

    // Terminal for the list query
    chain.then = (resolve: (v: unknown) => unknown) =>
      Promise.resolve({ data: [], error: null, count: 0 }).then(resolve)

    // insert chain — .select().single()
    const insertChain: Record<string, unknown> = {}
    insertChain.select = vi.fn(() => insertChain)
    insertChain.single = vi.fn(() => Promise.resolve(insertResult))

    chain.insert = vi.fn((row: unknown) => {
      recordedInserts.push({ table, row })
      return insertChain
    })

    chain.upsert = vi.fn((row: unknown, opts: unknown) => {
      recordedUpserts.push({ table, row, opts })
      return insertChain
    })

    return chain
  }

  const supabase = {
    auth: {
      getClaims: vi.fn(() =>
        Promise.resolve(
          authenticated
            ? { data: { claims: { sub: 'user-1' } }, error: null }
            : { data: null, error: null }
        )
      ),
    },
    from: vi.fn((table: string) => makeQueryChain(table)),
    _recordedFilters: recordedFilters,
    _recordedInserts: recordedInserts,
    _recordedUpserts: recordedUpserts,
  }

  return supabase
}

// ─── Module mock wiring ───────────────────────────────────────────────────────

let currentMock = makeSupabaseMock()

vi.mock('@/lib/supabase/server', () => ({
  createClient: vi.fn(() => Promise.resolve(currentMock)),
}))

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

beforeEach(() => {
  currentMock = makeSupabaseMock()
  vi.clearAllMocks()
})

// Lazy-import so the mock is in place first.
async function getHandler() {
  const mod = await import('../route')
  return mod
}

function makeGetRequest(searchParams: Record<string, string> = {}) {
  const url = new URL('http://localhost/api/v1/oee')
  for (const [k, v] of Object.entries(searchParams)) url.searchParams.set(k, v)
  return new Request(url.toString())
}

function makePostRequest(body: unknown) {
  return new Request('http://localhost/api/v1/oee', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  })
}

// ─── GET ──────────────────────────────────────────────────────────────────────

describe('GET /api/v1/oee', () => {
  it('returns 401 when no claims are present', async () => {
    currentMock = makeSupabaseMock({ authenticated: false })
    const { GET } = await getHandler()
    const res = await GET(makeGetRequest())
    expect(res.status).toBe(401)
  })

  it('returns 200 with an empty list when authenticated', async () => {
    const { GET } = await getHandler()
    const res = await GET(makeGetRequest())
    expect(res.status).toBe(200)
    const body = await res.json()
    expect(body.data).toEqual([])
  })

  it('applies week_from filter using calendar_week column (not week_number)', async () => {
    const { GET } = await getHandler()
    await GET(makeGetRequest({ week_from: '10' }))
    const gteFilter = currentMock._recordedFilters.find(
      (f) => f.method === 'gte'
    )
    expect(gteFilter).toBeDefined()
    expect(gteFilter!.col).toBe('calendar_week')
    expect(gteFilter!.val).toBe(10)
  })

  it('applies week_to filter using calendar_week column (not week_number)', async () => {
    const { GET } = await getHandler()
    await GET(makeGetRequest({ week_to: '25' }))
    const lteFilter = currentMock._recordedFilters.find(
      (f) => f.method === 'lte'
    )
    expect(lteFilter).toBeDefined()
    expect(lteFilter!.col).toBe('calendar_week')
    expect(lteFilter!.val).toBe(25)
  })

  it('applies year filter when provided', async () => {
    const { GET } = await getHandler()
    await GET(makeGetRequest({ year: '2026' }))
    const yearFilter = currentMock._recordedFilters.find(
      (f) => f.method === 'eq' && f.col === 'year'
    )
    expect(yearFilter).toBeDefined()
    expect(yearFilter!.val).toBe(2026)
  })
})

// ─── POST ─────────────────────────────────────────────────────────────────────

describe('POST /api/v1/oee', () => {
  it('returns 401 when no claims are present', async () => {
    currentMock = makeSupabaseMock({ authenticated: false })
    const { POST } = await getHandler()
    const res = await POST(makePostRequest({ line_name: 'L1', year: 2026, week_number: 10 }))
    expect(res.status).toBe(401)
  })

  it('returns 400 when required fields are missing (no line_name)', async () => {
    const { POST } = await getHandler()
    const res = await POST(makePostRequest({ year: 2026, week_number: 10 }))
    expect(res.status).toBe(400)
  })

  it('returns 400 when required fields are missing (no year)', async () => {
    const { POST } = await getHandler()
    const res = await POST(makePostRequest({ line_name: 'L1', week_number: 10 }))
    expect(res.status).toBe(400)
  })

  it('returns 400 when required fields are missing (no week_number)', async () => {
    const { POST } = await getHandler()
    const res = await POST(makePostRequest({ line_name: 'L1', year: 2026 }))
    expect(res.status).toBe(400)
  })

  it('maps request body week_number → DB column calendar_week on upsert', async () => {
    const { POST } = await getHandler()
    const res = await POST(
      makePostRequest({ line_name: 'Assembly-1', year: 2026, week_number: 42 })
    )
    expect(res.status).toBe(201)

    // The upserted row must contain calendar_week, not week_number
    const upsertedRecord = currentMock._recordedUpserts[0]
    expect(upsertedRecord).toBeDefined()
    const row = upsertedRecord.row as Record<string, unknown>
    expect(row.calendar_week).toBe(42)
    expect(row).not.toHaveProperty('week_number')
  })

  it('passes other fields through to the upsert unchanged', async () => {
    const { POST } = await getHandler()
    await POST(
      makePostRequest({
        line_name: 'Assembly-1',
        year: 2026,
        week_number: 5,
        oee_percent: 82.5,
      })
    )
    const row = currentMock._recordedUpserts[0].row as Record<string, unknown>
    expect(row.line_name).toBe('Assembly-1')
    expect(row.year).toBe(2026)
    expect(row.oee_percent).toBe(82.5)
  })

  it('upserts on the project-scoped conflict target (idempotent, no duplicates)', async () => {
    const { POST } = await getHandler()
    await POST(makePostRequest({ line_name: 'Assembly-1', year: 2026, week_number: 7 }))

    // POST must upsert (not plain insert) so repeat writes are idempotent
    expect(currentMock._recordedInserts).toHaveLength(0)
    expect(currentMock._recordedUpserts).toHaveLength(1)
    const opts = currentMock._recordedUpserts[0].opts as { onConflict?: string }
    expect(opts.onConflict).toBe('project_id,line_name,calendar_week,year')
  })
})
