import { authPool } from '../db/authDb.js'
import { AuthError } from '../auth/authService.js'
import { type PermTreeNode, buildPermTree, type PermTreeResponse } from './permResolver.js'

const PROTECTED_ROLE_CODES = ['ADMIN']

/** AUTH_PERM_TREE에서 level 0 리소스 코드를 동적 조회 */
async function getTopLevelResourceCodes(): Promise<string[]> {
  const result = await authPool.query(
    `SELECT RSRC_CD FROM AUTH_PERM_TREE WHERE RSRC_LEVEL = 0 AND USE_YN = 'Y' ORDER BY SORT_ORD`
  )
  return result.rows.map((r: { rsrc_cd: string }) => r.rsrc_cd)
}

/** AUTH_PERM_TREE 전체 노드 조회 */
export async function getPermTreeNodes(): Promise<PermTreeNode[]> {
  const result = await authPool.query(
    `SELECT RSRC_CD as code, PARENT_CD as "parentCode", RSRC_NM as name,
            RSRC_DESC as description, ICON as icon, RSRC_LEVEL as level, SORT_ORD as "sortOrder"
     FROM AUTH_PERM_TREE WHERE USE_YN = 'Y'
     ORDER BY RSRC_LEVEL, SORT_ORD`
  )
  return result.rows
}

/** 트리 구조로 변환하여 반환 (프론트엔드 UI용) */
export async function getPermTree(): Promise<PermTreeResponse[]> {
  const nodes = await getPermTreeNodes()
  return buildPermTree(nodes)
}

interface RoleWithPermissions {
  sn: number
  code: string
  name: string
  description: string | null
  isDefault: boolean
  permissions: Array<{
    sn: number
    resourceCode: string
    operationCode: string
    granted: boolean
  }>
}

interface CreateRoleInput {
  code: string
  name: string
  description?: string
}

interface UpdateRoleInput {
  name?: string
  description?: string
}

export async function listRolesWithPermissions(): Promise<RoleWithPermissions[]> {
  const rolesResult = await authPool.query(
    `SELECT ROLE_SN as sn, ROLE_CD as code, ROLE_NM as name, ROLE_DC as description, DFLT_YN as is_default
     FROM AUTH_ROLE ORDER BY ROLE_SN`
  )

  const roles: RoleWithPermissions[] = []

  for (const row of rolesResult.rows) {
    const permsResult = await authPool.query(
      `SELECT PERM_SN as sn, RSRC_CD as resource_code, OPER_CD as operation_code, GRANT_YN as granted
       FROM AUTH_PERM WHERE ROLE_SN = $1 ORDER BY RSRC_CD, OPER_CD`,
      [row.sn]
    )

    roles.push({
      sn: row.sn,
      code: row.code,
      name: row.name,
      description: row.description,
      isDefault: row.is_default === 'Y',
      permissions: permsResult.rows.map((p: {
        sn: number; resource_code: string; operation_code: string; granted: string
      }) => ({
        sn: p.sn,
        resourceCode: p.resource_code,
        operationCode: p.operation_code,
        granted: p.granted === 'Y',
      })),
    })
  }

  return roles
}

export async function updateRoleDefault(roleSn: number, isDefault: boolean): Promise<void> {
  await authPool.query(
    'UPDATE AUTH_ROLE SET DFLT_YN = $1 WHERE ROLE_SN = $2',
    [isDefault ? 'Y' : 'N', roleSn]
  )
}

export async function createRole(input: CreateRoleInput): Promise<RoleWithPermissions> {
  if (!/^[A-Z][A-Z0-9_]{0,18}[A-Z0-9]$/.test(input.code)) {
    throw new AuthError('역할 코드는 영문 대문자, 숫자, 언더스코어만 허용되며 2~20자입니다.', 400)
  }

  const client = await authPool.connect()
  try {
    await client.query('BEGIN')

    const existing = await client.query(
      'SELECT 1 FROM AUTH_ROLE WHERE ROLE_CD = $1', [input.code]
    )
    if (existing.rows.length > 0) {
      throw new AuthError('이미 존재하는 역할 코드입니다.', 409)
    }

    const result = await client.query(
      `INSERT INTO AUTH_ROLE (ROLE_CD, ROLE_NM, ROLE_DC) VALUES ($1, $2, $3)
       RETURNING ROLE_SN as sn, ROLE_CD as code, ROLE_NM as name, ROLE_DC as description, DFLT_YN as is_default`,
      [input.code, input.name, input.description || null]
    )
    const row = result.rows[0]

    // 새 역할: level 0 리소스에 READ='N' 초기화
    const topLevelCodes = await getTopLevelResourceCodes()
    for (const rsrc of topLevelCodes) {
      await client.query(
        'INSERT INTO AUTH_PERM (ROLE_SN, RSRC_CD, OPER_CD, GRANT_YN) VALUES ($1, $2, $3, $4)',
        [row.sn, rsrc, 'READ', 'N']
      )
    }

    await client.query('COMMIT')

    const permsResult = await authPool.query(
      `SELECT PERM_SN as sn, RSRC_CD as resource_code, OPER_CD as operation_code, GRANT_YN as granted
       FROM AUTH_PERM WHERE ROLE_SN = $1 ORDER BY RSRC_CD, OPER_CD`,
      [row.sn]
    )

    return {
      sn: row.sn,
      code: row.code,
      name: row.name,
      description: row.description,
      isDefault: row.is_default === 'Y',
      permissions: permsResult.rows.map((p: {
        sn: number; resource_code: string; operation_code: string; granted: string
      }) => ({
        sn: p.sn,
        resourceCode: p.resource_code,
        operationCode: p.operation_code,
        granted: p.granted === 'Y',
      })),
    }
  } catch (err) {
    await client.query('ROLLBACK')
    throw err
  } finally {
    client.release()
  }
}

export async function updateRole(roleSn: number, input: UpdateRoleInput): Promise<void> {
  const roleResult = await authPool.query(
    'SELECT ROLE_CD FROM AUTH_ROLE WHERE ROLE_SN = $1', [roleSn]
  )
  if (roleResult.rows.length === 0) {
    throw new AuthError('역할을 찾을 수 없습니다.', 404)
  }

  const sets: string[] = []
  const params: (string | number | null)[] = []
  let idx = 1

  if (input.name !== undefined) {
    sets.push(`ROLE_NM = $${idx++}`)
    params.push(input.name)
  }
  if (input.description !== undefined) {
    sets.push(`ROLE_DC = $${idx++}`)
    params.push(input.description)
  }

  if (sets.length === 0) {
    throw new AuthError('수정할 항목이 없습니다.', 400)
  }

  params.push(roleSn)
  await authPool.query(
    `UPDATE AUTH_ROLE SET ${sets.join(', ')} WHERE ROLE_SN = $${idx}`,
    params
  )
}

export async function deleteRole(roleSn: number): Promise<void> {
  const roleResult = await authPool.query(
    'SELECT ROLE_CD FROM AUTH_ROLE WHERE ROLE_SN = $1', [roleSn]
  )
  if (roleResult.rows.length === 0) {
    throw new AuthError('역할을 찾을 수 없습니다.', 404)
  }

  if (PROTECTED_ROLE_CODES.includes(roleResult.rows[0].role_cd)) {
    throw new AuthError('시스템 보호 역할은 삭제할 수 없습니다.', 403)
  }

  await authPool.query('DELETE FROM AUTH_ROLE WHERE ROLE_SN = $1', [roleSn])
}

export async function updatePermissions(
  roleSn: number,
  permissions: Array<{ resourceCode: string; operationCode: string; granted: boolean }>
): Promise<void> {
  for (const perm of permissions) {
    const existing = await authPool.query(
      'SELECT PERM_SN FROM AUTH_PERM WHERE ROLE_SN = $1 AND RSRC_CD = $2 AND OPER_CD = $3',
      [roleSn, perm.resourceCode, perm.operationCode]
    )

    if (existing.rows.length > 0) {
      await authPool.query(
        'UPDATE AUTH_PERM SET GRANT_YN = $1 WHERE ROLE_SN = $2 AND RSRC_CD = $3 AND OPER_CD = $4',
        [perm.granted ? 'Y' : 'N', roleSn, perm.resourceCode, perm.operationCode]
      )
    } else {
      await authPool.query(
        'INSERT INTO AUTH_PERM (ROLE_SN, RSRC_CD, OPER_CD, GRANT_YN) VALUES ($1, $2, $3, $4)',
        [roleSn, perm.resourceCode, perm.operationCode, perm.granted ? 'Y' : 'N']
      )
    }
  }
}
