import type {
  CheckInBadge,
  CheckInBadgeCode,
  CheckInBadgeDefinition,
  CheckInEligibility,
  CheckInLocationSnapshot,
  CheckInProfile,
  CheckInRecord,
  CheckInResult,
} from './types'
import type { GeoPoint } from '@/domain/poi'

export const CHECK_IN_POINTS = 10
export const CHECK_IN_RADIUS_METERS = 200
export const CHECK_IN_MAX_ACCURACY_METERS = 100

export const CHECK_IN_BADGES: readonly CheckInBadgeDefinition[] = [
  {
    code: 'explorer',
    name: '光明探索者',
    description: '完成首个真实定位点位打卡',
    icon: '01',
    requiredCheckIns: 1,
  },
  {
    code: 'traveller',
    name: '光明旅行家',
    description: '累计打卡 2 个开放点位',
    icon: '02',
    requiredCheckIns: 2,
  },
  {
    code: 'check_in_master',
    name: '全域打卡达人',
    description: '累计打卡 3 个开放点位',
    icon: '03',
    requiredCheckIns: 3,
  },
]

export class CheckInError extends Error {
  constructor(
    message: string,
    readonly code: 'already_checked_in' | 'invalid_poi' | 'invalid_location' | 'not_eligible',
  ) {
    super(message)
    this.name = 'CheckInError'
  }
}

export function createEmptyCheckInProfile(): CheckInProfile {
  return { points: 0, badges: [], checkIns: [] }
}

export function cloneCheckInProfile(profile: CheckInProfile): CheckInProfile {
  return {
    points: profile.points,
    badges: profile.badges.map(badge => ({ ...badge })),
    checkIns: profile.checkIns.map(record => ({ ...record })),
  }
}

function toRadians(value: number): number {
  return value * Math.PI / 180
}

function isValidPoint(point: GeoPoint): boolean {
  return Number.isFinite(point.longitude)
    && Number.isFinite(point.latitude)
    && point.longitude >= -180
    && point.longitude <= 180
    && point.latitude >= -90
    && point.latitude <= 90
}

export function distanceBetweenMeters(left: GeoPoint, right: GeoPoint): number {
  if (!isValidPoint(left) || !isValidPoint(right))
    return Number.NaN

  const earthRadiusMeters = 6_371_008.8
  const latitudeDelta = toRadians(right.latitude - left.latitude)
  const longitudeDelta = toRadians(right.longitude - left.longitude)
  const leftLatitude = toRadians(left.latitude)
  const rightLatitude = toRadians(right.latitude)
  const haversine = Math.sin(latitudeDelta / 2) ** 2
    + Math.cos(leftLatitude) * Math.cos(rightLatitude) * Math.sin(longitudeDelta / 2) ** 2
  return earthRadiusMeters * 2 * Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine))
}

export function createCheckInRecordId(poiId: string, checkedInAt: string): string {
  return `check-in:${poiId}:${checkedInAt}`
}

export function evaluateCheckInEligibility(
  location: CheckInLocationSnapshot,
  poi: GeoPoint,
  allowedDistanceMeters = CHECK_IN_RADIUS_METERS,
): CheckInEligibility {
  if (!Number.isFinite(allowedDistanceMeters) || allowedDistanceMeters <= 0) {
    return {
      eligible: false,
      code: 'invalid_location',
      distanceMeters: null,
      allowedDistanceMeters: CHECK_IN_RADIUS_METERS,
      message: '点位打卡范围配置无效，请稍后重试',
    }
  }
  if (location.coordinateSystem !== 'GCJ02' || !isValidPoint(location)) {
    return {
      eligible: false,
      code: 'invalid_location',
      distanceMeters: null,
      allowedDistanceMeters,
      message: '定位结果无效，请重新获取当前位置',
    }
  }

  const accuracy = location.accuracy
  if (accuracy === null || !Number.isFinite(accuracy) || accuracy <= 0 || accuracy > CHECK_IN_MAX_ACCURACY_METERS) {
    return {
      eligible: false,
      code: 'low_accuracy',
      distanceMeters: null,
      allowedDistanceMeters,
      message: '当前位置精度不足，请到开阔处重新定位',
    }
  }

  const distanceMeters = distanceBetweenMeters(location, poi)
  if (!Number.isFinite(distanceMeters)) {
    return {
      eligible: false,
      code: 'invalid_location',
      distanceMeters: null,
      allowedDistanceMeters,
      message: '定位结果无效，请重新获取当前位置',
    }
  }

  if (distanceMeters - accuracy > allowedDistanceMeters) {
    return {
      eligible: false,
      code: 'too_far',
      distanceMeters,
      allowedDistanceMeters,
      message: `请到点位 ${Math.round(allowedDistanceMeters)} 米范围内再打卡`,
    }
  }

  if (distanceMeters + accuracy > allowedDistanceMeters) {
    return {
      eligible: false,
      code: 'uncertain',
      distanceMeters,
      allowedDistanceMeters,
      message: '当前位置接近打卡边界，请靠近点位后重新定位',
    }
  }

  return {
    eligible: true,
    code: 'eligible',
    distanceMeters,
    allowedDistanceMeters,
    message: '已进入点位打卡范围',
  }
}

export function hasCheckedIn(profile: CheckInProfile, poiId: string): boolean {
  const normalizedPoiId = poiId.trim()
  return Boolean(normalizedPoiId) && profile.checkIns.some(record => record.poiId === normalizedPoiId)
}

function badgesForCheckInCount(checkInCount: number, obtainedAt: string): CheckInBadge[] {
  return CHECK_IN_BADGES
    .filter(definition => checkInCount >= definition.requiredCheckIns)
    .map(definition => ({ code: definition.code, obtainedAt }))
}

export function applyCheckIn(
  profile: CheckInProfile,
  poi: { id: string, name: string } & GeoPoint,
  location: CheckInLocationSnapshot,
  now = Date.now(),
  allowedDistanceMeters = CHECK_IN_RADIUS_METERS,
): CheckInResult {
  const poiId = poi.id.trim()
  const poiName = poi.name.trim()
  if (!poiId || !poiName || !isValidPoint(poi))
    throw new CheckInError('点位信息无效，请返回地图重新选择', 'invalid_poi')
  if (hasCheckedIn(profile, poiId))
    throw new CheckInError('该点位已经打卡，本机不会重复计分', 'already_checked_in')

  const eligibility = evaluateCheckInEligibility(location, poi, allowedDistanceMeters)
  if (!eligibility.eligible || eligibility.distanceMeters === null)
    throw new CheckInError(eligibility.message, 'not_eligible')

  const checkedInAt = new Date(now).toISOString()
  const record: CheckInRecord = {
    id: createCheckInRecordId(poiId, checkedInAt),
    poiId,
    poiName,
    checkedInAt,
    pointsAwarded: CHECK_IN_POINTS,
  }
  const previousBadgeCodes = new Set<CheckInBadgeCode>(profile.badges.map(badge => badge.code))
  const earnedBadges = badgesForCheckInCount(profile.checkIns.length + 1, checkedInAt)
  const newBadges = earnedBadges.filter(badge => !previousBadgeCodes.has(badge.code))
  const nextProfile: CheckInProfile = {
    points: profile.points + CHECK_IN_POINTS,
    badges: [
      ...profile.badges.map(badge => ({ ...badge })),
      ...newBadges,
    ],
    checkIns: [record, ...profile.checkIns.map(item => ({ ...item }))],
  }
  return { profile: nextProfile, record: { ...record }, newBadges: newBadges.map(badge => ({ ...badge })) }
}
