/**
 * @fileoverview
 * Koa 기반의 GeoJSON → MBTiles 변환 및 VectorTile(MVT) 서비스 제공 라우터
 *
 * 이 라우터는 GeoJSON 데이터를 MBTiles 형식으로 변환하고, OpenLayers 등의 클라이언트에서
 * Vector Tile(MVT) 형식으로 요청할 수 있도록 API 엔드포인트를 제공합니다.
 *
 * 📌 주요 기능:
 * 1️⃣ `/tiles/regenerate`  (POST) → GeoJSON을 다시 MBTiles로 변환
 * 2️⃣ `/tiles/:z/:x/:y.mvt` (GET) → 특정 타일 데이터를 반환
 * 3️⃣ `/tiles/metadata`  (GET) → MBTiles의 메타데이터 반환
 *
 * 🚀 서비스 초기화 흐름:
 * 1. 서버 시작 시 `initializeTileService()` 호출
 *    - MBTiles 파일이 없으면 `convertGeoJsonToMBTiles()` 실행하여 생성
 *    - MBTiles가 존재하면 `loadTileSource()`를 호출하여 데이터 로드
 * 2. 클라이언트가 MVT 타일을 요청하면 `tileSource.getTile(z, x, y, callback)`을 통해 데이터 반환
 *
 * 📌 주요 처리 로직:
 * ✅ **GeoJSON → MBTiles 변환 (`convertGeoJsonToMBTiles`)**
 *    - `tippecanoe` CLI를 사용하여 GeoJSON을 MBTiles로 변환
 *    - 기존 MBTiles 파일이 있으면 삭제 후 재생성
 *    - `spawn`을 사용하여 비동기적으로 실행하며 로그 출력
 *
 * ✅ **MBTiles 파일을 로드 (`loadTileSource`)**
 *    - `@mapbox/mbtiles` 라이브러리를 사용하여 MBTiles 데이터베이스를 로드
 *    - 타일 요청이 있을 때 해당 타일을 `tileSource.getTile()`을 통해 제공
 *
 * ✅ **타일 요청 처리 (`/tiles/:z/:x/:y.mvt`)**
 *    - MBTiles에서 특정 타일을 찾아 바이너리 MVT 데이터로 반환
 *    - `Content-Encoding: gzip` 설정으로 압축된 타일 제공
 *    - `TMS 좌표 변환` 문제 제거하여 직접 요청 가능하도록 개선
 *
 * ✅ **타일셋 메타데이터 반환 (`/tiles/metadata`)**
 *    - MBTiles 파일에서 메타데이터 정보를 가져와 JSON 형태로 반환
 *
 * 📌 종속성:
 * - `koa-router`: Koa 기반 API 라우팅
 * - `fs`, `path`: 파일 시스템 접근 및 경로 처리
 * - `child_process`, `util`: `tippecanoe` 실행을 위한 비동기 프로세스 실행
 * - `@mapbox/mbtiles`: MBTiles 데이터베이스 접근 라이브러리
 * - `@things-factory/env`: 환경 변수 관리
 *
 * 📌 디렉토리 구조:
 * ```
 * /cache/mvt-tiles/        # 생성된 MBTiles 저장
 * /data/geo-json/          # 원본 GeoJSON 파일 저장
 * ```
 *
 * 📌 실행 방법:
 * 1. GeoJSON 데이터 준비
 * 2. Koa 서버 실행 → 자동으로 MBTiles 변환 후 서비스 시작
 * 3. 클라이언트(OpenLayers 등)에서 VectorTile 요청 가능
 * 4. 필요 시 `/tiles/regenerate` 엔드포인트로 타일 재생성
 *
 * @module geoRouter
 */

import Router from 'koa-router'
import * as fs from 'fs'
import * as path from 'path'
import * as child_process from 'child_process'
import * as util from 'util'
import { config } from '@things-factory/env'
import mbtiles from '@mapbox/mbtiles'

const exec = util.promisify(child_process.exec)

export const geoRouter = new Router()

const TILE_CACHE_DIR = path.resolve(config.get('directory', './cache'), 'mvt-tiles')
const GEO_DATA_DIR = path.resolve(__dirname, '..', '..', 'data', 'geo-json')

if (!fs.existsSync(TILE_CACHE_DIR)) {
  fs.mkdirSync(TILE_CACHE_DIR, { recursive: true })
}
if (!fs.existsSync(GEO_DATA_DIR)) {
  fs.mkdirSync(GEO_DATA_DIR, { recursive: true })
}

const COUNTRIES_GEO_JSON = path.resolve(GEO_DATA_DIR, 'countries-max-geo.json')
const MBTILES_PATH = path.resolve(TILE_CACHE_DIR, 'countries.mbtiles')

import { spawn } from 'child_process'

const convertGeoJsonToMBTiles = async () => {
  // tippecanoe -o ./packages/operato-ecs/cache/mvt-tiles/countries.mbtiles -z10 -Z5 --force  --maximum-zoom=10 --minimum-zoom=0 --drop-smallest-as-needed --layer=countries ./packages/weather/data/geo-json/countries-max-geo.json
  try {
    if (!fs.existsSync(COUNTRIES_GEO_JSON)) {
      console.error('GeoJSON 파일을 찾을 수 없습니다:', COUNTRIES_GEO_JSON)
      return false
    }

    if (fs.existsSync(MBTILES_PATH)) {
      fs.unlinkSync(MBTILES_PATH)
      console.log('기존 MBTiles 파일 삭제됨')
    }

    const tippecanoeCmd = [
      '-o',
      MBTILES_PATH,
      '-z10',
      '-Z5',
      '--force',
      '--maximum-zoom=10',
      '--minimum-zoom=0',
      '--drop-smallest-as-needed',
      '--layer=countries',
      COUNTRIES_GEO_JSON
    ]

    console.log('Tippecanoe 명령 실행 중...')
    console.log(`tippecanoe ${tippecanoeCmd.join(' ')}`)

    return new Promise((resolve, reject) => {
      const process = spawn('tippecanoe', tippecanoeCmd)

      process.stdout.on('data', data => {
        console.log(`[tippecanoe]: ${data.toString()}`)
      })

      process.stderr.on('data', data => {
        console.error(`[tippecanoe ERROR]: ${data.toString()}`)
      })

      process.on('close', code => {
        if (code === 0) {
          console.log('MBTiles 변환 완료 ✅')
          resolve(true)
        } else {
          console.error(`Tippecanoe 종료 코드: ${code}`)
          resolve(false)
        }
      })

      process.on('error', err => {
        console.error(`Tippecanoe 실행 오류: ${err}`)
        reject(err)
      })
    })
  } catch (error) {
    console.error('MBTiles 변환 오류:', error)
    return false
  }
}

// MBTiles 소스
let tileSource = null

// MBTiles 소스 로드
const loadTileSource = async () => {
  if (tileSource) return tileSource

  return new Promise((resolve, reject) => {
    new mbtiles(`mbtiles://${MBTILES_PATH}`, (err, source) => {
      if (err) {
        console.error('MBTiles 로드 오류:', err)
        return reject(err)
      }

      tileSource = source
      console.log('MBTiles 소스 로드 완료')
      resolve(source)
    })
  })
}

// 타일 서비스 초기화
const initializeTileService = async () => {
  try {
    // MBTiles 파일이 없으면 생성
    if (!fs.existsSync(MBTILES_PATH)) {
      console.log('MBTiles 파일이 없습니다. 생성을 시작합니다...')
      const result = await convertGeoJsonToMBTiles()
      if (!result) return false
    }

    // MBTiles 소스 로드
    await loadTileSource()
    return true
  } catch (error) {
    console.error('타일 서비스 초기화 오류:', error)
    return false
  }
}

// API 엔드포인트: 타일 재생성 트리거
geoRouter.post('/tiles/regenerate', async ctx => {
  try {
    console.log('타일 재생성 요청 받음')
    const result = await convertGeoJsonToMBTiles()

    if (result) {
      // 새 타일소스 로드
      await loadTileSource()
      ctx.body = { success: true, message: 'MBTiles 타일 생성 완료' }
    } else {
      ctx.status = 500
      ctx.body = { success: false, message: 'MBTiles 타일 생성 실패' }
    }
  } catch (error) {
    console.error('타일 재생성 오류:', error)
    ctx.status = 500
    ctx.body = { success: false, message: '서버 오류 발생' }
  }
})

geoRouter.get('/tiles/:z/:x/:y.mvt', async ctx => {
  try {
    const z = parseInt(ctx.params.z, 10)
    const x = parseInt(ctx.params.x, 10)
    const y = parseInt(ctx.params.y, 10)

    // 타일 서비스 로드 확인
    if (!tileSource) {
      console.log(`🚀 타일 서비스 로드 중...`)
      const initialized = await initializeTileService()
      if (!initialized) {
        ctx.status = 500
        ctx.body = { error: '타일 서비스를 초기화할 수 없습니다.' }
        return
      }
    }

    await new Promise((resolve, reject) => {
      tileSource.getTile(z, x, y, (err, data, headers) => {
        if (err) {
          if (err.message.includes('Tile does not exist')) {
            ctx.status = 204
            console.log(`🚨 타일 없음: z=${z}, x=${x}, y=${y}`)
            return resolve(null)
          }

          console.error(`❌ 타일 로드 오류 (z:${z}, x:${x}, y:${y}):`, err)
          ctx.status = 500
          ctx.body = { error: '타일을 로드할 수 없습니다.' }
          return resolve(null)
        }

        ctx.set('Content-Type', headers['Content-Type'] || 'application/x-protobuf')
        ctx.set({
          'Access-Control-Allow-Origin': '*',
          'Cache-Control': 'no-cache, no-store, must-revalidate, proxy-revalidate',
          'Content-Encoding': 'gzip'
        })

        ctx.body = data
        resolve(null)
      })
    })
  } catch (error) {
    console.error('❌ 타일 서비스 오류:', error)
    ctx.status = 500
    ctx.body = { error: '서버 오류가 발생했습니다.' }
  }
})

geoRouter.get('/tiles/metadata', async ctx => {
  try {
    if (!tileSource) {
      const initialized = await initializeTileService()
      if (!initialized) {
        ctx.status = 500
        ctx.body = { error: '타일 서비스를 초기화할 수 없습니다.' }
        return
      }
    }

    await new Promise((resolve, reject) => {
      tileSource.getInfo((err, info) => {
        if (err) {
          console.error('메타데이터 로드 오류:', err)
          ctx.status = 500
          ctx.body = { error: '메타데이터를 로드할 수 없습니다.' }
          return resolve(null)
        }

        ctx.body = info
        resolve(null)
      })
    })
  } catch (error) {
    console.error('메타데이터 서비스 오류:', error)
    ctx.status = 500
    ctx.body = { error: '서버 오류가 발생했습니다.' }
  }
})

initializeTileService().then(success => {
  if (success) {
    console.log('🗺️ 타일 서비스가 준비되었습니다.')
  } else {
    console.error('❌ 타일 서비스 초기화에 실패했습니다.')
  }
})

export default geoRouter
