import type { PlanningRequest } from '@/domain/travel'

interface RemoteAPIError {
  detail?: unknown
}

export interface RemotePoiCandidate {
  id: string
  name: string
  categoryCode: string
  tagCodes: string[]
  summary: string
  recommendationIndex: number
}

export interface RemotePoiCandidateInput {
  id: string
  name: string
  categoryCode: string
  tagCodes: readonly string[]
  summary: string
  recommendationIndex: number
}

export interface RemoteRecommendationItem {
  poiId: string
  reason: string
  order: number
}

export interface RemoteRecommendationResponse {
  requestId: string
  mode: PlanningRequest['mode']
  assistantMessage: string
  recommendations: RemoteRecommendationItem[]
  generationMode: 'mock' | 'real'
}

function apiBaseUrl(): string {
  const value = import.meta.env.VITE_TRAVEL_ASSISTANT_API_BASE_URL
  return typeof value === 'string' ? value.trim().replace(/\/+$/, '') : ''
}

export function isRemoteTravelAssistantEnabled(): boolean {
  return apiBaseUrl().length > 0
}

function remoteUrl(path: string): string {
  const baseUrl = apiBaseUrl()
  if (!/^https?:\/\//i.test(baseUrl))
    throw new Error('VITE_TRAVEL_ASSISTANT_API_BASE_URL 必须是有效的 HTTP(S) 地址')
  return `${baseUrl}${path}`
}

function errorDetail(data: unknown, statusCode: number): string {
  if (typeof data === 'object' && data !== null) {
    const detail = (data as RemoteAPIError).detail
    if (typeof detail === 'string' && detail.trim())
      return detail.trim()
  }
  return `行程服务请求失败（${statusCode}）`
}

function request<T>(options: UniApp.RequestOptions): Promise<T> {
  return new Promise((resolve, reject) => {
    uni.request({
      ...options,
      success(response) {
        if (response.statusCode >= 200 && response.statusCode < 300) {
          resolve(response.data as T)
          return
        }
        reject(new Error(errorDetail(response.data, response.statusCode)))
      },
      fail() {
        reject(new Error('无法连接行程服务，请检查网络、服务地址和微信合法域名配置'))
      },
    })
  })
}

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

function readNonEmptyString(value: unknown, fieldName: string, maxLength: number): string {
  if (typeof value !== 'string' || !value.trim() || value.trim().length > maxLength)
    throw new Error(`AI 推荐响应中的${fieldName}格式无效`)
  return value.trim()
}

function safeCandidates(candidates: readonly RemotePoiCandidateInput[]): RemotePoiCandidate[] {
  if (candidates.length < 1 || candidates.length > 100)
    throw new Error('发送给 AI 的 POI 候选数量必须为 1 至 100 个')

  const ids = new Set<string>()
  return candidates.map((candidate) => {
    const id = candidate.id.trim()
    if (!id || ids.has(id))
      throw new Error(`发送给 AI 的 POI ID 无效或重复：${id || '空值'}`)
    const name = candidate.name.trim()
    const categoryCode = candidate.categoryCode.trim()
    const summary = candidate.summary.trim()
    if (!name || name.length > 100)
      throw new Error(`发送给 AI 的 POI 名称无效：${id}`)
    if (!categoryCode || categoryCode.length > 64)
      throw new Error(`发送给 AI 的 POI 分类无效：${id}`)
    if (summary.length > 300)
      throw new Error(`发送给 AI 的 POI 摘要过长：${id}`)
    if (!Number.isInteger(candidate.recommendationIndex)
      || candidate.recommendationIndex < 1
      || candidate.recommendationIndex > 5) {
      throw new Error(`发送给 AI 的 POI 推荐指数无效：${id}`)
    }
    const tagCodes = [...new Set(candidate.tagCodes.map(tagCode => tagCode.trim()).filter(Boolean))]
    if (tagCodes.length > 20 || tagCodes.some(tagCode => tagCode.length > 64))
      throw new Error(`发送给 AI 的 POI 标签无效：${id}`)
    ids.add(id)
    return {
      id,
      name,
      categoryCode,
      tagCodes,
      summary,
      recommendationIndex: candidate.recommendationIndex,
    }
  })
}

function normalizeRecommendations(
  value: unknown,
  planningRequest: PlanningRequest,
  candidates: readonly RemotePoiCandidate[],
): RemoteRecommendationResponse {
  if (!isRecord(value))
    throw new Error('AI 推荐响应格式无效')
  if (value.mode !== planningRequest.mode)
    throw new Error('AI 推荐响应的规划模式不匹配')
  if (value.generationMode !== 'mock' && value.generationMode !== 'real')
    throw new Error('AI 推荐响应的生成模式无效')
  if (!Array.isArray(value.recommendations) || value.recommendations.length < 1 || value.recommendations.length > 8)
    throw new Error('AI 推荐响应必须包含 1 至 8 个 POI')

  const allowedIds = new Set(candidates.map(candidate => candidate.id))
  const seenIds = new Set<string>()
  const seenOrders = new Set<number>()
  const recommendations = value.recommendations.map((item): RemoteRecommendationItem => {
    if (!isRecord(item))
      throw new Error('AI 推荐项格式无效')
    const poiId = readNonEmptyString(item.poiId, 'POI ID', 100)
    if (!allowedIds.has(poiId))
      throw new Error(`AI 返回了候选白名单外的 POI：${poiId}`)
    if (seenIds.has(poiId))
      throw new Error(`AI 返回了重复 POI：${poiId}`)
    if (!Number.isInteger(item.order) || (item.order as number) < 1 || (item.order as number) > 8)
      throw new Error('AI 推荐响应中的顺序格式无效')
    if (seenOrders.has(item.order as number))
      throw new Error('AI 推荐响应中存在重复顺序')
    seenIds.add(poiId)
    seenOrders.add(item.order as number)
    return {
      poiId,
      reason: readNonEmptyString(item.reason, '推荐理由', 300),
      order: item.order as number,
    }
  }).sort((left, right) => left.order - right.order)

  if (recommendations.some((item, index) => item.order !== index + 1))
    throw new Error('AI 推荐响应中的顺序必须从 1 连续递增')
  if (planningRequest.mode === 'custom') {
    const selectedIds = new Set(planningRequest.selectedPoiIds)
    if (recommendations.length !== selectedIds.size
      || recommendations.some(item => !selectedIds.has(item.poiId))) {
      throw new Error('AI 自选规划未完整保留用户选择的 POI')
    }
  }

  return {
    requestId: readNonEmptyString(value.requestId, '请求 ID', 200),
    mode: planningRequest.mode,
    assistantMessage: readNonEmptyString(value.assistantMessage, '助手消息', 1000),
    recommendations,
    generationMode: value.generationMode,
  }
}

/**
 * Requests coordinate-free POI recommendations. The caller must calculate the
 * final route and duration locally; planningRequest.origin is never uploaded.
 */
export async function createRemoteRecommendations(
  planningRequest: PlanningRequest,
  candidateInputs: readonly RemotePoiCandidateInput[],
): Promise<RemoteRecommendationResponse> {
  const candidates = safeCandidates(candidateInputs)
  const response = await request<unknown>({
    url: remoteUrl('/api/v1/plans'),
    method: 'POST',
    data: {
      mode: planningRequest.mode,
      preferences: {
        destination: planningRequest.preferences.destination,
        themes: [...planningRequest.preferences.themes],
        pace: planningRequest.preferences.pace,
        interests: [...planningRequest.preferences.interests],
        transport: planningRequest.preferences.transport,
      },
      durationMinutes: planningRequest.durationMinutes,
      selectedPoiIds: [...planningRequest.selectedPoiIds],
      candidates,
    },
    header: { 'content-type': 'application/json' },
    timeout: 50000,
  })
  return normalizeRecommendations(response, planningRequest, candidates)
}
