import express from 'express'
import { wingPool } from '../db/wingDb.js'
import { enrichLayerWithMetadata } from '../utils/layerIcons.js'
import {
  sanitizeParams,
  sanitizeString,
  isValidNumber,
  isValidStringLength,
} from '../middleware/security.js'
import { requireAuth, requireRole } from '../auth/authMiddleware.js'

const router = express.Router()

interface Layer {
  cmn_cd: string
  up_cmn_cd: string | null
  cmn_cd_full_nm: string
  cmn_cd_nm: string
  cmn_cd_level: number
  clnm: string | null
  data_tbl_nm: string | null
}

// DB 컬럼 → API 응답 컬럼 매핑 (프론트엔드 호환성 유지)
const LAYER_COLUMNS = `
  LAYER_CD      AS cmn_cd,
  UP_LAYER_CD   AS up_cmn_cd,
  LAYER_FULL_NM AS cmn_cd_full_nm,
  LAYER_NM      AS cmn_cd_nm,
  LAYER_LEVEL   AS cmn_cd_level,
  WMS_LAYER_NM  AS clnm,
  DATA_TBL_NM   AS data_tbl_nm
`.trim()

// 조상 중 하나라도 USE_YN='N'이면 제외하는 재귀 CTE
// 부모가 비활성화되면 자식도 공개 API에서 제외됨 (상속 방식)
const ACTIVE_TREE_CTE = `
WITH RECURSIVE active_tree AS (
  SELECT LAYER_CD FROM LAYER
  WHERE UP_LAYER_CD IS NULL AND USE_YN = 'Y' AND DEL_YN = 'N'
  UNION ALL
  SELECT l.LAYER_CD FROM LAYER l
  JOIN active_tree a ON l.UP_LAYER_CD = a.LAYER_CD
  WHERE l.USE_YN = 'Y' AND l.DEL_YN = 'N'
)
`.trim()

// 모든 라우트에 파라미터 살균 적용
router.use(sanitizeParams)

// 모든 레이어 조회
router.get('/', async (_req, res) => {
  try {
    const { rows } = await wingPool.query<Layer>(
      `${ACTIVE_TREE_CTE}
       SELECT ${LAYER_COLUMNS} FROM LAYER
       WHERE LAYER_CD IN (SELECT LAYER_CD FROM active_tree)
       ORDER BY LAYER_CD`
    )
    const enrichedLayers = rows.map(enrichLayerWithMetadata)
    res.json(enrichedLayers)
  } catch {
    res.status(500).json({ error: '레이어 조회 실패' })
  }
})

// 계층 구조로 변환된 레이어 트리 조회
router.get('/tree/all', async (_req, res) => {
  try {
    const { rows } = await wingPool.query<Layer>(
      `${ACTIVE_TREE_CTE}
       SELECT ${LAYER_COLUMNS} FROM LAYER
       WHERE LAYER_CD IN (SELECT LAYER_CD FROM active_tree)
       ORDER BY LAYER_CD`
    )
    const enrichedLayers = rows.map(enrichLayerWithMetadata)

    const layerMap = new Map<string, Layer & { children: Layer[] }>()
    enrichedLayers.forEach(layer => {
      layerMap.set(layer.cmn_cd, { ...layer, children: [] })
    })

    const rootLayers: (Layer & { children: Layer[] })[] = []
    enrichedLayers.forEach(layer => {
      const layerNode = layerMap.get(layer.cmn_cd)!
      if (layer.up_cmn_cd === null) {
        rootLayers.push(layerNode)
      } else {
        const parent = layerMap.get(layer.up_cmn_cd)
        if (parent) {
          parent.children.push(layerNode)
        }
      }
    })

    res.json(rootLayers)
  } catch {
    res.status(500).json({ error: '레이어 트리 조회 실패' })
  }
})

// WMS 레이어만 조회
router.get('/wms/all', async (_req, res) => {
  try {
    const { rows } = await wingPool.query<Layer>(
      `${ACTIVE_TREE_CTE}
       SELECT ${LAYER_COLUMNS} FROM LAYER
       WHERE LAYER_CD IN (SELECT LAYER_CD FROM active_tree) AND WMS_LAYER_NM IS NOT NULL
       ORDER BY LAYER_CD`
    )
    const enrichedLayers = rows.map(enrichLayerWithMetadata)
    res.json(enrichedLayers)
  } catch {
    res.status(500).json({ error: 'WMS 레이어 조회 실패' })
  }
})

// 특정 레벨의 레이어만 조회
router.get('/level/:level', async (req, res) => {
  try {
    const level = parseInt(req.params.level, 10)

    if (!isValidNumber(level, 1, 10)) {
      return res.status(400).json({
        error: '유효하지 않은 레벨값',
        message: '레벨은 1~10 범위의 정수여야 합니다.'
      })
    }

    const { rows } = await wingPool.query<Layer>(
      `${ACTIVE_TREE_CTE}
       SELECT ${LAYER_COLUMNS} FROM LAYER
       WHERE LAYER_CD IN (SELECT LAYER_CD FROM active_tree) AND LAYER_LEVEL = $1
       ORDER BY LAYER_CD`,
      [level]
    )
    const enrichedLayers = rows.map(enrichLayerWithMetadata)
    res.json(enrichedLayers)
  } catch {
    res.status(500).json({ error: '레벨별 레이어 조회 실패' })
  }
})

// 특정 부모의 자식 레이어 조회
router.get('/children/:parentId', async (req, res) => {
  try {
    const parentId = req.params.parentId

    if (!parentId || !isValidStringLength(parentId, 50) || !/^[a-zA-Z0-9_-]+$/.test(parentId)) {
      return res.status(400).json({
        error: '유효하지 않은 부모 ID',
        message: 'ID는 영숫자, 언더스코어, 하이픈만 허용됩니다.'
      })
    }

    const sanitizedId = sanitizeString(parentId)
    const { rows } = await wingPool.query<Layer>(
      `SELECT ${LAYER_COLUMNS} FROM LAYER WHERE UP_LAYER_CD = $1 AND USE_YN = 'Y' AND DEL_YN = 'N' ORDER BY LAYER_CD`,
      [sanitizedId]
    )
    const enrichedLayers = rows.map(enrichLayerWithMetadata)
    res.json(enrichedLayers)
  } catch {
    res.status(500).json({ error: '자식 레이어 조회 실패' })
  }
})

// 특정 레이어 조회
router.get('/:id', async (req, res) => {
  try {
    const id = req.params.id

    if (!id || !isValidStringLength(id, 50) || !/^[a-zA-Z0-9_-]+$/.test(id)) {
      return res.status(400).json({
        error: '유효하지 않은 레이어 ID',
        message: 'ID는 영숫자, 언더스코어, 하이픈만 허용됩니다.'
      })
    }

    const sanitizedId = sanitizeString(id)
    const { rows } = await wingPool.query<Layer>(
      `SELECT ${LAYER_COLUMNS} FROM LAYER WHERE LAYER_CD = $1 AND DEL_YN = 'N'`,
      [sanitizedId]
    )
    if (rows.length === 0) {
      return res.status(404).json({ error: '레이어를 찾을 수 없습니다' })
    }
    const enrichedLayer = enrichLayerWithMetadata(rows[0])
    res.json(enrichedLayer)
  } catch {
    res.status(500).json({ error: '레이어 조회 실패' })
  }
})

// ── 관리자 전용 엔드포인트 ──────────────────────────────────────

// 전체 레이어 목록 (페이지네이션 + 검색/필터, USE_YN 무관)
router.get('/admin/list', requireAuth, requireRole('ADMIN'), async (req, res) => {
  try {
    const page = Math.max(1, parseInt(String(req.query.page ?? '1'), 10) || 1)
    const limit = Math.min(100, Math.max(1, parseInt(String(req.query.limit ?? '10'), 10) || 10))
    const offset = (page - 1) * limit
    const search = sanitizeString(String(req.query.search ?? '')).trim()
    const useYnFilter = String(req.query.useYn ?? '')

    // 동적 WHERE 절 구성 (DEL_YN = 'N' 기본 조건)
    const conditions: string[] = ["DEL_YN = 'N'"]
    const params: (string | number)[] = []

    if (search) {
      params.push(`%${search}%`)
      const n = params.length
      conditions.push(`(LAYER_CD ILIKE $${n} OR LAYER_NM ILIKE $${n} OR LAYER_FULL_NM ILIKE $${n})`)
    }

    if (useYnFilter === 'Y' || useYnFilter === 'N') {
      params.push(useYnFilter)
      conditions.push(`USE_YN = $${params.length}`)
    }

    const rootCd = sanitizeString(String(req.query.rootCd ?? '')).trim()
    if (rootCd) {
      if (!/^[a-zA-Z0-9_-]+$/.test(rootCd) || !isValidStringLength(rootCd, 50)) {
        return res.status(400).json({ error: '유효하지 않은 루트 레이어코드' })
      }
      params.push(`${rootCd}%`)
      conditions.push(`LAYER_CD LIKE $${params.length}`)
    }

    const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''

    // 데이터 쿼리 파라미터: WHERE 조건 + LIMIT + OFFSET
    const dataParams = [...params, limit, offset]
    const limitIdx = dataParams.length - 1
    const offsetIdx = dataParams.length

    const [dataResult, countResult] = await Promise.all([
      wingPool.query(
        `SELECT
          t.*,
          p.USE_YN AS "parentUseYn"
        FROM (
          SELECT
            LAYER_CD      AS "layerCd",
            UP_LAYER_CD   AS "upLayerCd",
            LAYER_FULL_NM AS "layerFullNm",
            LAYER_NM      AS "layerNm",
            LAYER_LEVEL   AS "layerLevel",
            WMS_LAYER_NM  AS "wmsLayerNm",
            DATA_TBL_NM   AS "dataTblNm",
            USE_YN        AS "useYn",
            SORT_ORD      AS "sortOrd",
            TO_CHAR(REG_DTM, 'YYYY-MM-DD') AS "regDtm"
          FROM LAYER
          ${whereClause}
          ORDER BY LAYER_CD
          LIMIT $${limitIdx} OFFSET $${offsetIdx}
        ) t
        LEFT JOIN LAYER p ON t."upLayerCd" = p.LAYER_CD AND p.DEL_YN = 'N'
        ORDER BY t."layerCd"`,
        dataParams
      ),
      wingPool.query(
        `SELECT COUNT(*)::int AS total FROM LAYER ${whereClause}`,
        params
      ),
    ])

    const total: number = countResult.rows[0].total
    res.json({
      items: dataResult.rows,
      total,
      page,
      totalPages: Math.ceil(total / limit),
    })
  } catch {
    res.status(500).json({ error: '레이어 목록 조회 실패' })
  }
})

// 드롭다운용 레이어 옵션 목록
router.get('/admin/options', requireAuth, requireRole('ADMIN'), async (_req, res) => {
  try {
    const { rows } = await wingPool.query(
      `SELECT LAYER_CD AS "layerCd", LAYER_NM AS "layerNm",
              LAYER_FULL_NM AS "layerFullNm", LAYER_LEVEL AS "layerLevel"
       FROM LAYER WHERE DEL_YN = 'N' ORDER BY LAYER_CD`
    )
    res.json(rows)
  } catch {
    res.status(500).json({ error: '레이어 옵션 조회 실패' })
  }
})

// 상위코드 기반 다음 자식 코드 계산
router.get('/admin/next-code', requireAuth, requireRole('ADMIN'), async (req, res) => {
  const upLayerCd = sanitizeString(String(req.query.upLayerCd ?? '')).trim()
  if (!upLayerCd || !/^[a-zA-Z0-9_-]+$/.test(upLayerCd) || !isValidStringLength(upLayerCd, 50)) {
    return res.status(400).json({ error: '유효하지 않은 상위 레이어코드' })
  }
  try {
    const { rows } = await wingPool.query(
      `SELECT LAYER_CD AS "layerCd" FROM LAYER
       WHERE UP_LAYER_CD = $1 AND DEL_YN = 'N'
       ORDER BY LAYER_CD DESC LIMIT 1`,
      [upLayerCd]
    )
    let nextCode: string
    if (rows.length === 0) {
      nextCode = upLayerCd + '001'
    } else {
      const lastCd = rows[0].layerCd as string
      const suffix = lastCd.substring(upLayerCd.length)
      const num = parseInt(suffix, 10)
      nextCode = isNaN(num)
        ? upLayerCd + '001'
        : upLayerCd + String(num + 1).padStart(suffix.length, '0')
    }
    res.json({ nextCode })
  } catch {
    res.status(500).json({ error: '다음 레이어코드 계산 실패' })
  }
})

// 레이어 생성
router.post('/admin/create', requireAuth, requireRole('ADMIN'), async (req, res) => {
  try {
    const body = req.body as {
      layerCd?: string
      upLayerCd?: string
      layerFullNm?: string
      layerNm?: string
      layerLevel?: number
      wmsLayerNm?: string
      dataTblNm?: string
      useYn?: string
      sortOrd?: number
    }

    const { layerCd, upLayerCd, layerFullNm, layerNm, layerLevel, wmsLayerNm, dataTblNm, useYn, sortOrd } = body

    // 필수 필드 검증
    if (!layerCd || !isValidStringLength(layerCd, 50) || !/^[a-zA-Z0-9_-]+$/.test(layerCd)) {
      return res.status(400).json({ error: '유효하지 않은 레이어코드입니다. 영숫자, 언더스코어, 하이픈만 허용됩니다.' })
    }
    if (!layerNm || !isValidStringLength(layerNm, 100)) {
      return res.status(400).json({ error: '레이어명은 필수이며 100자 이내여야 합니다.' })
    }
    if (!layerFullNm || !isValidStringLength(layerFullNm, 200)) {
      return res.status(400).json({ error: '레이어 전체명은 필수이며 200자 이내여야 합니다.' })
    }
    if (layerLevel === undefined || layerLevel === null || !isValidNumber(layerLevel, 1, 10)) {
      return res.status(400).json({ error: '레이어 레벨은 1~10 범위의 정수여야 합니다.' })
    }

    // 선택 필드 검증
    if (upLayerCd !== undefined && upLayerCd !== null && upLayerCd !== '') {
      if (!isValidStringLength(upLayerCd, 50) || !/^[a-zA-Z0-9_-]+$/.test(upLayerCd)) {
        return res.status(400).json({ error: '유효하지 않은 상위 레이어코드입니다.' })
      }
    }
    if (wmsLayerNm !== undefined && wmsLayerNm !== null && wmsLayerNm !== '') {
      if (!isValidStringLength(wmsLayerNm, 100)) {
        return res.status(400).json({ error: 'WMS 레이어명은 100자 이내여야 합니다.' })
      }
    }
    if (dataTblNm !== undefined && dataTblNm !== null && dataTblNm !== '') {
      if (!isValidStringLength(dataTblNm, 100) || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(dataTblNm)) {
        return res.status(400).json({ error: '데이터 테이블명은 100자 이내의 유효한 PostgreSQL 테이블명이어야 합니다.' })
      }
    }

    const sanitizedLayerCd = sanitizeString(layerCd)
    const sanitizedUpLayerCd = upLayerCd ? sanitizeString(upLayerCd) : null
    const sanitizedLayerFullNm = sanitizeString(layerFullNm)
    const sanitizedLayerNm = sanitizeString(layerNm)
    const sanitizedWmsLayerNm = wmsLayerNm ? sanitizeString(wmsLayerNm) : null
    const sanitizedDataTblNm = dataTblNm ? sanitizeString(dataTblNm) : null
    const sanitizedUseYn = useYn === 'N' ? 'N' : 'Y'
    const sanitizedSortOrd = typeof sortOrd === 'number' ? sortOrd : null

    const { rows } = await wingPool.query(
      `INSERT INTO LAYER (LAYER_CD, UP_LAYER_CD, LAYER_FULL_NM, LAYER_NM, LAYER_LEVEL, WMS_LAYER_NM, DATA_TBL_NM, USE_YN, SORT_ORD, DEL_YN)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 'N')
       RETURNING LAYER_CD AS "layerCd"`,
      [sanitizedLayerCd, sanitizedUpLayerCd, sanitizedLayerFullNm, sanitizedLayerNm, layerLevel, sanitizedWmsLayerNm, sanitizedDataTblNm, sanitizedUseYn, sanitizedSortOrd]
    )

    res.json(rows[0])
  } catch (err) {
    const pgErr = err as { code?: string }
    if (pgErr.code === '23505') {
      return res.status(409).json({ error: '이미 존재하는 레이어코드입니다.' })
    }
    res.status(500).json({ error: '레이어 생성 실패' })
  }
})

// 레이어 수정
router.post('/admin/update', requireAuth, requireRole('ADMIN'), async (req, res) => {
  try {
    const body = req.body as {
      layerCd?: string
      upLayerCd?: string
      layerFullNm?: string
      layerNm?: string
      layerLevel?: number
      wmsLayerNm?: string
      dataTblNm?: string
      useYn?: string
      sortOrd?: number
    }

    const { layerCd, upLayerCd, layerFullNm, layerNm, layerLevel, wmsLayerNm, dataTblNm, useYn, sortOrd } = body

    // 필수 필드 검증
    if (!layerCd || !isValidStringLength(layerCd, 50) || !/^[a-zA-Z0-9_-]+$/.test(layerCd)) {
      return res.status(400).json({ error: '유효하지 않은 레이어코드입니다. 영숫자, 언더스코어, 하이픈만 허용됩니다.' })
    }
    if (!layerNm || !isValidStringLength(layerNm, 100)) {
      return res.status(400).json({ error: '레이어명은 필수이며 100자 이내여야 합니다.' })
    }
    if (!layerFullNm || !isValidStringLength(layerFullNm, 200)) {
      return res.status(400).json({ error: '레이어 전체명은 필수이며 200자 이내여야 합니다.' })
    }
    if (layerLevel === undefined || layerLevel === null || !isValidNumber(layerLevel, 1, 10)) {
      return res.status(400).json({ error: '레이어 레벨은 1~10 범위의 정수여야 합니다.' })
    }

    // 선택 필드 검증
    if (upLayerCd !== undefined && upLayerCd !== null && upLayerCd !== '') {
      if (!isValidStringLength(upLayerCd, 50) || !/^[a-zA-Z0-9_-]+$/.test(upLayerCd)) {
        return res.status(400).json({ error: '유효하지 않은 상위 레이어코드입니다.' })
      }
    }
    if (wmsLayerNm !== undefined && wmsLayerNm !== null && wmsLayerNm !== '') {
      if (!isValidStringLength(wmsLayerNm, 100)) {
        return res.status(400).json({ error: 'WMS 레이어명은 100자 이내여야 합니다.' })
      }
    }
    if (dataTblNm !== undefined && dataTblNm !== null && dataTblNm !== '') {
      if (!isValidStringLength(dataTblNm, 100) || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(dataTblNm)) {
        return res.status(400).json({ error: '데이터 테이블명은 100자 이내의 유효한 PostgreSQL 테이블명이어야 합니다.' })
      }
    }

    const sanitizedLayerCd = sanitizeString(layerCd)
    const sanitizedUpLayerCd = upLayerCd ? sanitizeString(upLayerCd) : null
    const sanitizedLayerFullNm = sanitizeString(layerFullNm)
    const sanitizedLayerNm = sanitizeString(layerNm)
    const sanitizedWmsLayerNm = wmsLayerNm ? sanitizeString(wmsLayerNm) : null
    const sanitizedDataTblNm = dataTblNm ? sanitizeString(dataTblNm) : null
    const sanitizedUseYn = useYn === 'N' ? 'N' : 'Y'
    const sanitizedSortOrd = typeof sortOrd === 'number' ? sortOrd : null

    const { rows } = await wingPool.query(
      `UPDATE LAYER
       SET UP_LAYER_CD = $2, LAYER_FULL_NM = $3, LAYER_NM = $4, LAYER_LEVEL = $5,
           WMS_LAYER_NM = $6, DATA_TBL_NM = $7, USE_YN = $8, SORT_ORD = $9
       WHERE LAYER_CD = $1
       RETURNING LAYER_CD AS "layerCd"`,
      [sanitizedLayerCd, sanitizedUpLayerCd, sanitizedLayerFullNm, sanitizedLayerNm, layerLevel, sanitizedWmsLayerNm, sanitizedDataTblNm, sanitizedUseYn, sanitizedSortOrd]
    )

    if (rows.length === 0) {
      return res.status(404).json({ error: '레이어를 찾을 수 없습니다' })
    }
    res.json(rows[0])
  } catch (err) {
    const pgErr = err as { code?: string }
    if (pgErr.code === '23505') {
      return res.status(409).json({ error: '이미 존재하는 레이어코드입니다.' })
    }
    res.status(500).json({ error: '레이어 수정 실패' })
  }
})

// 레이어 삭제
router.post('/admin/delete', requireAuth, requireRole('ADMIN'), async (req, res) => {
  try {
    const { layerCd } = req.body as { layerCd?: string }

    if (!layerCd || !isValidStringLength(layerCd, 50) || !/^[a-zA-Z0-9_-]+$/.test(layerCd)) {
      return res.status(400).json({ error: '유효하지 않은 레이어코드입니다.' })
    }

    const sanitizedCd = sanitizeString(layerCd)

    // 하위 레이어 존재 여부 확인 (자식이 있으면 삭제 차단)
    const { rows: childRows } = await wingPool.query(
      `SELECT COUNT(*)::int AS cnt FROM LAYER WHERE UP_LAYER_CD = $1 AND DEL_YN = 'N'`,
      [sanitizedCd]
    )
    const childCount: number = childRows[0].cnt
    if (childCount > 0) {
      return res.status(400).json({
        error: `하위 레이어 ${childCount}개가 있어 삭제할 수 없습니다. 하위 레이어를 먼저 삭제해주세요.`,
      })
    }

    const { rows } = await wingPool.query(
      `UPDATE LAYER SET DEL_YN = 'Y' WHERE LAYER_CD = $1 AND DEL_YN = 'N'
       RETURNING LAYER_CD AS "layerCd"`,
      [sanitizedCd]
    )

    if (rows.length === 0) {
      return res.status(404).json({ error: '레이어를 찾을 수 없습니다' })
    }
    res.json(rows[0])
  } catch {
    res.status(500).json({ error: '레이어 삭제 실패' })
  }
})

// USE_YN 토글
router.post('/admin/toggle-use', requireAuth, requireRole('ADMIN'), async (req, res) => {
  try {
    const { layerCd } = req.body as { layerCd?: string }

    if (!layerCd || !isValidStringLength(layerCd, 50) || !/^[a-zA-Z0-9_-]+$/.test(layerCd)) {
      return res.status(400).json({ error: '유효하지 않은 레이어코드' })
    }

    const sanitizedCd = sanitizeString(layerCd)
    const { rows } = await wingPool.query(
      `UPDATE LAYER
        SET USE_YN = CASE WHEN USE_YN = 'Y' THEN 'N' ELSE 'Y' END
       WHERE LAYER_CD = $1
       RETURNING LAYER_CD AS "layerCd", USE_YN AS "useYn"`,
      [sanitizedCd]
    )

    if (rows.length === 0) {
      return res.status(404).json({ error: '레이어를 찾을 수 없습니다' })
    }
    res.json(rows[0])
  } catch {
    res.status(500).json({ error: 'USE_YN 변경 실패' })
  }
})

export default router
