import { describe, expect, it } from 'vitest'
import { poiDataset } from '../src/data/poi/dataset'
import { createPoiRepository } from '../src/data/poi/repository'
import {
  filterPois,
  findPoiById,
  formatOpeningHours,
  formatRecommendationLabel,
  type PoiDataset,
  validatePoiDataset,
} from '../src/domain/poi'

function cloneDataset(): PoiDataset {
  return JSON.parse(JSON.stringify(poiDataset)) as PoiDataset
}

describe('poi POC dataset', () => {
  it('is usable while explicitly limiting its claim to a reviewed sample', () => {
    const result = validatePoiDataset(poiDataset)

    expect(result.isDatasetUsable).toBe(true)
    expect(result.isValid).toBe(true)
    expect(result.invalidPoiIds).toEqual([])
    expect(result.validPois).toHaveLength(poiDataset.pois.length)
    expect(result.validPois.length).toBeGreaterThanOrEqual(20)
    expect(poiDataset.meta.coverageScope).toBe('reviewed_sample')
    expect(poiDataset.meta.authoritativeBaselineProvided).toBe(false)
    expect(poiDataset.meta.canClaimFullCoverage).toBe(false)
    expect(poiDataset.meta.coverageLabel).toContain('非全域权威清单')
    // All 30 POIs have real photos, so no placeholder warnings remain
    expect(result.issues.filter(issue => issue.code === 'POC_PLACEHOLDER_ASSET')).toHaveLength(0)
  })

  it('provides the seven requested presentation fields for every sample POI', () => {
    for (const poi of poiDataset.pois) {
      expect(poi.name.trim()).not.toBe('')
      expect(poi.coverImageId.trim()).not.toBe('')
      expect(Number.isFinite(poi.longitude)).toBe(true)
      expect(Number.isFinite(poi.latitude)).toBe(true)
      expect(poi.coordinateSystem).toBe('GCJ02')
      expect(poi.description.trim()).not.toBe('')
      expect(poi.openingHours).toBeDefined()
      expect(poi.recommendationIndex).toBeGreaterThanOrEqual(1)
      expect(poi.recommendationIndex).toBeLessThanOrEqual(5)
      expect(poi.recommendationSource).toBe('poc_default')
      expect(poi.tagCodes.length).toBeGreaterThanOrEqual(1)
      expect(poi.tagCodes.length).toBeLessThanOrEqual(5)
      expect(poi.sourceReference).toMatch(/^amap:[A-Z0-9]+$/)
    }
  })
})

describe('poi dataset validation', () => {
  it('rejects a structurally invalid dataset as a whole', () => {
    const invalid = cloneDataset()
    invalid.categories[1].code = invalid.categories[0].code

    const result = validatePoiDataset(invalid)

    expect(result.isDatasetUsable).toBe(false)
    expect(result.validPois).toEqual([])
    expect(result.issues.some(issue => issue.code === 'DATASET_STRUCTURE_INVALID')).toBe(true)
  })

  it('returns a structural issue instead of throwing for malformed array members', () => {
    const malformed = cloneDataset() as unknown as Record<string, unknown>
    malformed.categories = [null]

    expect(() => validatePoiDataset(malformed)).not.toThrow()
    const result = validatePoiDataset(malformed)
    expect(result.isDatasetUsable).toBe(false)
    expect(result.issues).toEqual(expect.arrayContaining([
      expect.objectContaining({ code: 'DATASET_STRUCTURE_INVALID', path: '$' }),
    ]))
  })

  it('isolates a POI with invalid fields without hiding valid records', () => {
    const invalid = cloneDataset()
    invalid.pois[0].description = ''
    invalid.pois[0].tagCodes = []

    const result = validatePoiDataset(invalid)

    expect(result.isDatasetUsable).toBe(true)
    expect(result.isValid).toBe(false)
    expect(result.invalidPoiIds).toEqual(['poi_gm_culture_center'])
    expect(result.validPois).toHaveLength(poiDataset.pois.length - 1)
    expect(result.issues.some(issue => issue.code === 'REQUIRED_FIELD_MISSING')).toBe(true)
    expect(result.issues.some(issue => issue.code === 'TAG_INVALID')).toBe(true)
  })

  it('blocks invalid coordinate systems, out-of-area points and fake scores', () => {
    const invalid = cloneDataset()
    const target = invalid.pois[0] as unknown as {
      id: string
      coordinateSystem: string
      longitude: number
      latitude: number
      recommendationIndex: number
    }
    target.coordinateSystem = 'WGS84'
    target.longitude = 120
    target.latitude = 30
    target.recommendationIndex = 4.5

    const result = validatePoiDataset(invalid)
    const targetCodes = result.issues
      .filter(issue => issue.poiId === target.id)
      .map(issue => issue.code)

    expect(targetCodes).toContain('COORDINATE_SYSTEM_INVALID')
    expect(targetCodes).toContain('OUTSIDE_TARGET_AREA')
    expect(targetCodes).toContain('RECOMMENDATION_INVALID')
  })

  it('accepts both controlled recommendation sources and rejects unknown sources', () => {
    const pocDataset = cloneDataset()
    pocDataset.pois[0].recommendationSource = 'poc_default'

    const pocResult = validatePoiDataset(pocDataset)
    expect(pocResult.invalidPoiIds).not.toContain(pocDataset.pois[0].id)
    expect(pocResult.issues.some(issue => issue.code === 'RECOMMENDATION_INVALID')).toBe(false)

    const invalidDataset = cloneDataset()
    const invalidPoi = invalidDataset.pois[0] as unknown as { id: string, recommendationSource: string }
    invalidPoi.recommendationSource = 'amap_rating'

    const invalidResult = validatePoiDataset(invalidDataset)
    expect(invalidResult.invalidPoiIds).toContain(invalidPoi.id)
    expect(invalidResult.issues).toEqual(expect.arrayContaining([
      expect.objectContaining({
        code: 'RECOMMENDATION_INVALID',
        poiId: invalidPoi.id,
      }),
    ]))
  })

  it('does not allow a sample package to claim authoritative full coverage', () => {
    const invalid = cloneDataset()
    invalid.meta.canClaimFullCoverage = true

    const result = validatePoiDataset(invalid)

    expect(result.isDatasetUsable).toBe(false)
    expect(result.issues.some(issue => issue.path === 'meta.canClaimFullCoverage')).toBe(true)
  })

  it('warns about near-identical marker coordinates without blocking the dataset', () => {
    const nearby = cloneDataset()
    nearby.pois[1].longitude = nearby.pois[0].longitude
    nearby.pois[1].latitude = nearby.pois[0].latitude

    const result = validatePoiDataset(nearby)

    expect(result.isDatasetUsable).toBe(true)
    expect(result.invalidPoiIds).not.toContain(nearby.pois[1].id)
    expect(result.issues).toEqual(expect.arrayContaining([
      expect.objectContaining({
        code: 'POTENTIAL_COORDINATE_OVERLAP',
        severity: 'warning',
        poiId: nearby.pois[1].id,
      }),
    ]))
  })
})

describe('poi selection and repository', () => {
  it('filters by one category and an AND-combined set of controlled tags', () => {
    const pastoral = filterPois(poiDataset.pois, { categoryCode: 'pastoral_leisure' })
    const familyPhoto = filterPois(poiDataset.pois, { tagCodes: ['family_friendly', 'photo_spot'] })

    expect(pastoral.map(poi => poi.id)).toEqual([
      'poi_farm_grand_view',
      'poi_happy_pastoral',
      'poi_amap_b0ffkkmzsc',
    ])
    expect(familyPhoto.map(poi => poi.id)).toEqual(['poi_hongqiao_park', 'poi_happy_pastoral'])
  })

  it('finds a POI only by its stable ID', () => {
    expect(findPoiById(poiDataset.pois, 'poi_hongqiao_park')?.name).toBe('虹桥公园')
    expect(findPoiById(poiDataset.pois, '虹桥公园')).toBeNull()
    expect(findPoiById(poiDataset.pois, '  ')).toBeNull()
  })

  it('uses the mandated fallback for unknown opening hours', () => {
    expect(formatOpeningHours({
      status: 'unknown',
      displayText: null,
      note: null,
      sourceUpdatedAt: null,
    })).toBe('开放时间待确认')
  })

  it('formats recommendation labels consistently for all sources', () => {
    expect(formatRecommendationLabel('official_editorial', 5)).toBe('推荐指数 5/5')
    expect(formatRecommendationLabel('poc_default', 4)).toBe('推荐指数 4/5')
  })

  it('returns resolved summaries and details from one dataset version', () => {
    const repository = createPoiRepository()
    const summaries = repository.getPoiSummaries({ categoryCode: 'urban_park' })
    const detail = repository.getPoiById(summaries[0].id)

    expect(repository.getDatasetMeta().datasetVersion).toBe(poiDataset.meta.datasetVersion)
    expect(summaries.length).toBeGreaterThanOrEqual(10)
    expect(detail?.id).toBe(summaries[0].id)
    expect(detail?.category.code).toBe('urban_park')
    expect(detail?.coverImage.id).toBe(detail?.coverImageId)
    expect(detail?.openingHoursText).toBe('开放时间待确认')
    expect(summaries[0].recommendationSource).toBe(detail?.recommendationSource)
    expect(repository.getPoiById('missing')).toBeNull()
  })

  it('does not expose categories without published valid POIs', () => {
    const dataset = cloneDataset()
    dataset.categories.push({
      code: 'empty_category',
      name: '暂无点位',
      markerIcon: '/static/logo.png',
      selectedMarkerIcon: '/static/logo.png',
      sortOrder: 999,
      status: 'enabled',
    })

    const repository = createPoiRepository(dataset)
    expect(repository.getCategories().some(category => category.code === 'empty_category')).toBe(false)
  })

  it('returns defensive copies so page state cannot mutate the source package', () => {
    const repository = createPoiRepository()
    const firstRead = repository.getPoiById('poi_hongqiao_park')
    if (!firstRead)
      throw new Error('test fixture missing')

    firstRead.name = '被页面篡改'
    firstRead.tags.splice(0)

    const secondRead = repository.getPoiById('poi_hongqiao_park')
    expect(secondRead?.name).toBe('虹桥公园')
    expect(secondRead?.tags.length).toBeGreaterThan(0)
  })
})
