import type {
  DurationFitStatus,
  Itinerary,
  ItineraryItem,
  ItinerarySource,
  PlanningMode,
  PlanResponse,
  TravelAssistantSource,
} from './types'
import type { PoiRepository } from '@/domain/poi'
import { resolveCanonicalPoiId } from './poi-id'
import { TravelValidationError } from './preferences'

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value)
}

function readString(
  value: unknown,
  fieldName: string,
  options: { allowEmpty?: boolean, maxLength?: number } = {},
): string {
  if (typeof value !== 'string')
    throw new TravelValidationError(`${fieldName}格式无效`)
  const result = value.trim()
  if (!options.allowEmpty && !result)
    throw new TravelValidationError(`${fieldName}不能为空`)
  if (options.maxLength && result.length > options.maxLength)
    throw new TravelValidationError(`${fieldName}内容过长`)
  return result
}

function readStringArray(value: unknown, fieldName: string, maxItems: number): string[] {
  if (!Array.isArray(value) || value.length > maxItems)
    throw new TravelValidationError(`${fieldName}格式无效`)
  return value.map(item => readString(item, fieldName, { maxLength: 300 }))
}

function readInteger(value: unknown, fieldName: string, min: number, max: number): number {
  if (!Number.isInteger(value) || (value as number) < min || (value as number) > max)
    throw new TravelValidationError(`${fieldName}格式无效`)
  return value as number
}

function readPlanningMode(value: unknown): PlanningMode {
  if (value !== 'quick' && value !== 'custom')
    throw new TravelValidationError('规划模式无效')
  return value
}

function readDurationFitStatus(value: unknown): DurationFitStatus {
  if (value !== 'fits' && value !== 'underfilled' && value !== 'overflow')
    throw new TravelValidationError('行程时长适配状态无效')
  return value
}

function readBoolean(value: unknown, fieldName: string): boolean {
  if (typeof value !== 'boolean')
    throw new TravelValidationError(`${fieldName}格式无效`)
  return value
}

function readClock(value: unknown, fieldName: string): string {
  const result = readString(value, fieldName)
  const match = /^(\d{2}):(\d{2})$/.exec(result)
  if (!match || Number(match[1]) > 47 || Number(match[2]) > 59)
    throw new TravelValidationError(`${fieldName}必须为 HH:mm`)
  return result
}

function minutesFromClock(value: string): number {
  const [hours, minutes] = value.split(':').map(Number)
  return hours * 60 + minutes
}

function normalizeItem(value: unknown, repository: PoiRepository): ItineraryItem {
  if (!isRecord(value))
    throw new TravelValidationError('行程地点格式无效')

  const rawPlaceId = readString(value.placeId, '地点 ID')
  const placeId = resolveCanonicalPoiId(rawPlaceId, repository)
  if (!placeId)
    throw new TravelValidationError(`行程包含无法映射的地点 ID：${rawPlaceId}`)
  const poi = repository.getPoiById(placeId)
  if (!poi)
    throw new TravelValidationError(`行程地点已失效：${placeId}`)

  const startTime = readClock(value.startTime, '开始时间')
  const endTime = readClock(value.endTime, '结束时间')
  if (minutesFromClock(endTime) <= minutesFromClock(startTime))
    throw new TravelValidationError(`行程地点时间无效：${poi.name}`)

  const transfer = value.transferFromPreviousMinutes
  if (transfer !== null && (!Number.isInteger(transfer) || (transfer as number) < 0 || (transfer as number) > 240))
    throw new TravelValidationError(`交通时间格式无效：${poi.name}`)

  return {
    startTime,
    endTime,
    placeId,
    placeName: poi.name,
    activity: readString(value.activity, '游玩安排', { maxLength: 300 }),
    reason: readString(value.reason, '推荐理由', { maxLength: 500 }),
    transferFromPreviousMinutes: transfer as number | null,
    tips: readStringArray(value.tips, '出行提示', 10),
  }
}

function normalizeSource(value: unknown, repository: PoiRepository): ItinerarySource {
  if (!isRecord(value))
    throw new TravelValidationError('行程来源格式无效')

  const rawPlaceId = readString(value.placeId, '来源地点 ID')
  const placeId = resolveCanonicalPoiId(rawPlaceId, repository)
  if (!placeId)
    throw new TravelValidationError(`行程来源包含无法映射的地点 ID：${rawPlaceId}`)

  return {
    placeId,
    sourceName: readString(value.sourceName, '来源名称', { maxLength: 300 }),
    sourceUrl: readString(value.sourceUrl, '来源链接', { allowEmpty: true, maxLength: 1000 }),
    updatedAt: readString(value.updatedAt, '来源更新时间', { maxLength: 100 }),
  }
}

function normalizeItinerary(value: unknown, repository: PoiRepository): Itinerary {
  if (!isRecord(value))
    throw new TravelValidationError('行程格式无效')
  if (!Array.isArray(value.items) || value.items.length < 1 || value.items.length > 8)
    throw new TravelValidationError('行程地点数量必须为 1 至 8 个')
  if (!Array.isArray(value.sources))
    throw new TravelValidationError('行程来源格式无效')

  const items = value.items.map(item => normalizeItem(item, repository))
  const itemIds = new Set<string>()
  let previousEnd = -1
  for (const item of items) {
    if (itemIds.has(item.placeId))
      throw new TravelValidationError(`行程地点重复：${item.placeName}`)
    itemIds.add(item.placeId)
    const start = minutesFromClock(item.startTime)
    if (start < previousEnd)
      throw new TravelValidationError('行程时间顺序无效')
    previousEnd = minutesFromClock(item.endTime)
  }

  const sourcesById = new Map<string, ItinerarySource>()
  value.sources.forEach((source) => {
    const normalized = normalizeSource(source, repository)
    if (!itemIds.has(normalized.placeId))
      throw new TravelValidationError(`行程来源包含未使用地点：${normalized.placeId}`)
    if (!sourcesById.has(normalized.placeId))
      sourcesById.set(normalized.placeId, normalized)
  })
  itemIds.forEach((placeId) => {
    if (!sourcesById.has(placeId))
      throw new TravelValidationError(`行程地点缺少来源：${placeId}`)
  })

  return {
    title: readString(value.title, '行程标题', { maxLength: 200 }),
    summary: readString(value.summary, '行程摘要', { maxLength: 1000 }),
    totalMinutes: readInteger(value.totalMinutes, '行程总时长', 1, 2880),
    planningMode: readPlanningMode(value.planningMode),
    requestedMinutes: readInteger(value.requestedMinutes, '用户选择时长', 120, 600),
    durationFitStatus: readDurationFitStatus(value.durationFitStatus),
    startsFromCurrentLocation: readBoolean(value.startsFromCurrentLocation, '当前位置出发标记'),
    estimatedCostText: readString(value.estimatedCostText, '费用说明', { maxLength: 500 }),
    items,
    notes: readStringArray(value.notes, '行程说明', 20),
    sources: [...sourcesById.values()],
  }
}

export function normalizePlanResponse(
  value: unknown,
  repository: PoiRepository,
  sourceOverride?: TravelAssistantSource,
): PlanResponse {
  if (!isRecord(value))
    throw new TravelValidationError('行程响应格式无效')

  const source = sourceOverride ?? value.source
  if (source !== 'local_poc' && source !== 'remote_mock' && source !== 'remote_ai')
    throw new TravelValidationError('行程生成来源无效')
  const changeSummary = value.changeSummary
  if (changeSummary !== undefined && changeSummary !== null && typeof changeSummary !== 'string')
    throw new TravelValidationError('调整摘要格式无效')

  return {
    conversationId: readString(value.conversationId, '会话 ID', { maxLength: 200 }),
    assistantMessage: readString(value.assistantMessage, '助手消息', { maxLength: 1000 }),
    itinerary: normalizeItinerary(value.itinerary, repository),
    changeSummary: typeof changeSummary === 'string' ? changeSummary.trim() : changeSummary,
    source,
  }
}

export function clonePlanResponse(plan: PlanResponse, repository: PoiRepository): PlanResponse {
  return normalizePlanResponse(plan, repository)
}
