import { createClient } from '@/lib/supabase/server'
import { NextRequest } from 'next/server'
import { ok, list, badRequest, unauthorized, serverError, readPagination } from '@/lib/api'
import {
  getPrimaryProjectTypeId,
  getTypeCodes,
} from '@/lib/project-types/labels'

const PROJECT_SELECT_BASE =
  'id, created_at, user_id, supplier_id, project_statuses(id,code,label), supplier_master_data(id,supplier_name,city), project_type_assignments(project_type_code)'
const PROJECT_SELECT_TYPE_FILTER =
  'id, created_at, user_id, supplier_id, project_statuses(id,code,label), supplier_master_data(id,supplier_name,city), project_type_assignments!inner(project_type_code)'

export async function GET(request: NextRequest) {
  try {
  } catch (e) { if (e instanceof Response) return e; throw e }

  const supabase = await createClient()
  const { data: claimsData } = await supabase.auth.getClaims()
  if (!claimsData?.claims) return unauthorized()

  const { searchParams } = new URL(request.url)
  const { page, limit, offset } = readPagination(searchParams)
  const statusCode = searchParams.get('status')
  const typeCode = searchParams.get('type')

  const selectClause = typeCode ? PROJECT_SELECT_TYPE_FILTER : PROJECT_SELECT_BASE

  let query = supabase
    .from('projects')
    .select(selectClause, { count: 'exact' })
    .order('project_code', { ascending: false, nullsFirst: false })
    .range(offset, offset + limit - 1)

  if (statusCode) {
    query = query.eq('project_statuses.code', statusCode)
  }
  if (typeCode) {
    query = query.eq('project_type_assignments.project_type_code', typeCode)
  }

  const { data, error, count } = await query
  if (error) return serverError(error.message)

  // Master types for code→id lookup so the API can keep emitting a
  // compatibility project_type_id derived from the primary junction type.
  const { data: projectTypes } = await supabase
    .from('project_types')
    .select('id, code')
  const projectTypesByCode = new Map<string, { id: string }>(
    (projectTypes ?? []).map((t: { id: string; code: string }) => [t.code, { id: t.id }]),
  )

  const enriched = (data ?? []).map((project) => {
    const project_type_codes = getTypeCodes(project as { project_type_assignments?: Array<{ project_type_code: string }> | null })
    const project_type_id = getPrimaryProjectTypeId(
      project as { project_type_assignments?: Array<{ project_type_code: string }> | null },
      projectTypesByCode,
    )
    return {
      ...project,
      project_type_codes,
      project_type_id,
    }
  })

  return list(enriched, { page, limit, total: count ?? 0 })
}

export async function POST(request: NextRequest) {
  try {
  } catch (e) { if (e instanceof Response) return e; throw e }

  const supabase = await createClient()
  const { data: claimsData } = await supabase.auth.getClaims()
  if (!claimsData?.claims) return unauthorized()

  const body = await request.json()
  const { project_type_id, project_type_codes, supplier_id, ...rest } = body

  if (!supplier_id) {
    return badRequest('supplier_id is required')
  }

  // Master types fetched once: used both for legacy id→code translation and
  // for validating user-supplied project_type_codes BEFORE the project insert.
  // Only active types are accepted; inactive or unknown values are rejected.
  const { data: projectTypes } = await supabase
    .from('project_types')
    .select('id, code')
    .eq('is_active', true)
  const masterById = new Map<string, { code: string }>(
    (projectTypes ?? []).map((t: { id: string; code: string }) => [t.id, { code: t.code }]),
  )
  const validCodes = new Set<string>(
    (projectTypes ?? []).map((t: { id: string; code: string }) => t.code),
  )
  const projectTypesByCode = new Map<string, { id: string }>(
    (projectTypes ?? []).map((t: { id: string; code: string }) => [t.code, { id: t.id }]),
  )

  // Junction-first input. project_type_codes wins whenever the field is
  // present in the request body — even if empty or malformed — so legacy
  // project_type_id only acts as a compatibility fallback when the caller has
  // not explicitly addressed the new field.
  const hasProjectTypeCodes = Object.prototype.hasOwnProperty.call(body, 'project_type_codes')

  let normalizedCodes: string[] = []
  if (hasProjectTypeCodes) {
    if (!Array.isArray(project_type_codes)) {
      return badRequest('project_type_codes must be an array')
    }
    const candidates = Array.from(
      new Set(
        (project_type_codes as unknown[])
          .filter((c): c is string => typeof c === 'string')
          .map((c) => c.trim())
          .filter((c) => c.length > 0),
      ),
    )
    if (candidates.length === 0) {
      return badRequest('project_type_codes must contain at least one active project type code')
    }
    const invalid = candidates.filter((c) => !validCodes.has(c))
    if (invalid.length > 0) {
      return badRequest(`unknown or inactive project_type_codes: ${invalid.join(', ')}`)
    }
    normalizedCodes = candidates
  } else if (typeof project_type_id === 'string' && project_type_id.length > 0) {
    const code = masterById.get(project_type_id)?.code
    if (!code) {
      return badRequest('project_type_id does not match an active project type')
    }
    normalizedCodes = [code]
  } else {
    return badRequest('project_type_codes or project_type_id is required')
  }

  // Project insert without legacy project_type_id column. Junction is source of truth.
  const { data: newProject, error } = await supabase
    .from('projects')
    .insert({ ...rest, supplier_id, user_id: claimsData.claims.sub })
    .select()
    .single()

  if (error) return serverError(error.message)

  // Junction rows for assigned types. Codes already validated above.
  const assignmentRows = normalizedCodes.map((code) => ({
    project_id: newProject.id,
    project_type_code: code,
  }))
  const { error: assignErr } = await supabase
    .from('project_type_assignments')
    .insert(assignmentRows)
  if (assignErr) {
    // Best-effort cleanup of the orphan project so the caller does not see a
    // half-created row. RLS may block the delete (e.g. if the inserted project
    // is not visible to the caller); in that case the original error is still
    // returned and the orphan is documented in the response message.
    const { error: cleanupErr } = await supabase.from('projects').delete().eq('id', newProject.id)
    if (cleanupErr) {
      return serverError(
        `${assignErr.message} (orphan project ${newProject.id} could not be removed: ${cleanupErr.message})`,
      )
    }
    return serverError(assignErr.message)
  }

  // Mirror 2b3-ii-d enriched output shape.
  const projectWithJunction = {
    ...newProject,
    project_type_assignments: assignmentRows.map(({ project_type_code }) => ({ project_type_code })),
  }
  const enriched = {
    ...newProject,
    project_type_codes: normalizedCodes,
    project_type_id: getPrimaryProjectTypeId(projectWithJunction, projectTypesByCode),
  }

  return ok(enriched, { status: 201 })
}
