import bcrypt from 'bcrypt'
import { authPool } from '../db/authDb.js'
import { signToken, setTokenCookie } from './jwtProvider.js'
import type { Response } from 'express'
import { resolvePermissions, makePermKey, grantedSetToRecord } from '../roles/permResolver.js'
import { getPermTreeNodes } from '../roles/roleService.js'

const MAX_FAIL_COUNT = 5
const SALT_ROUNDS = 10

interface AuthUserRow {
  user_id: string
  user_acnt: string
  pswd_hash: string | null
  user_nm: string
  rnkp_nm: string | null
  org_sn: number | null
  user_stts_cd: string
  fail_cnt: number
}

interface AuthUserInfo {
  id: string
  account: string
  name: string
  rank: string | null
  org: { sn: number; name: string; abbr: string } | null
  roles: string[]
  permissions: Record<string, string[]>
}

export async function login(
  account: string,
  password: string,
  ipAddr: string,
  userAgent: string,
  res: Response
): Promise<AuthUserInfo> {
  const userResult = await authPool.query<AuthUserRow>(
    `SELECT USER_ID as user_id, USER_ACNT as user_acnt, PSWD_HASH as pswd_hash,
            USER_NM as user_nm, RNKP_NM as rnkp_nm, ORG_SN as org_sn,
            USER_STTS_CD as user_stts_cd, FAIL_CNT as fail_cnt
     FROM AUTH_USER WHERE USER_ACNT = $1`,
    [account]
  )

  if (userResult.rows.length === 0) {
    throw new AuthError('아이디 또는 비밀번호가 올바르지 않습니다.', 401)
  }

  const user = userResult.rows[0]

  if (user.user_stts_cd === 'PENDING') {
    throw new AuthError('계정이 승인 대기 중입니다. 관리자 승인 후 로그인할 수 있습니다.', 403)
  }

  if (user.user_stts_cd === 'LOCKED') {
    throw new AuthError('계정이 잠겨있습니다. 관리자에게 문의하세요.', 403)
  }

  if (user.user_stts_cd === 'INACTIVE') {
    throw new AuthError('비활성화된 계정입니다.', 403)
  }

  if (user.user_stts_cd === 'REJECTED') {
    throw new AuthError('가입이 거절된 계정입니다. 관리자에게 문의하세요.', 403)
  }

  if (!user.pswd_hash) {
    throw new AuthError('이 계정은 Google 로그인만 지원합니다.', 401)
  }

  const passwordValid = await bcrypt.compare(password, user.pswd_hash)

  if (!passwordValid) {
    const newFailCount = user.fail_cnt + 1
    const newStatus = newFailCount >= MAX_FAIL_COUNT ? 'LOCKED' : user.user_stts_cd

    await authPool.query(
      'UPDATE AUTH_USER SET FAIL_CNT = $1, USER_STTS_CD = $2, MDFCN_DTM = NOW() WHERE USER_ID = $3',
      [newFailCount, newStatus, user.user_id]
    )

    await recordLoginHistory(user.user_id, ipAddr, userAgent, false)

    if (newStatus === 'LOCKED') {
      throw new AuthError('로그인 실패 횟수 초과로 계정이 잠겼습니다.', 403)
    }

    throw new AuthError('아이디 또는 비밀번호가 올바르지 않습니다.', 401)
  }

  // 성공: FAIL_CNT 리셋, LAST_LOGIN_DTM 갱신
  await authPool.query(
    'UPDATE AUTH_USER SET FAIL_CNT = 0, LAST_LOGIN_DTM = NOW(), MDFCN_DTM = NOW() WHERE USER_ID = $1',
    [user.user_id]
  )

  await recordLoginHistory(user.user_id, ipAddr, userAgent, true)

  const userInfo = await getUserInfo(user.user_id)

  const token = signToken({
    sub: userInfo.id,
    acnt: userInfo.account,
    name: userInfo.name,
    roles: userInfo.roles,
  })

  setTokenCookie(res, token)

  return userInfo
}

/** AUTH_PERM_TREE 없이 플랫 권한을 RSRC_CD + OPER_CD 기준으로 조회 */
async function flatPermissionsFallback(userId: string): Promise<Record<string, string[]>> {
  const permsResult = await authPool.query(
    `SELECT DISTINCT p.RSRC_CD as rsrc_cd, p.OPER_CD as oper_cd
     FROM AUTH_PERM p
     JOIN AUTH_USER_ROLE ur ON p.ROLE_SN = ur.ROLE_SN
     WHERE ur.USER_ID = $1 AND p.GRANT_YN = 'Y'`,
    [userId]
  )
  const perms: Record<string, string[]> = {}
  for (const p of permsResult.rows) {
    if (!perms[p.rsrc_cd]) perms[p.rsrc_cd] = []
    if (!perms[p.rsrc_cd].includes(p.oper_cd)) perms[p.rsrc_cd].push(p.oper_cd)
  }
  return perms
}

export async function getUserInfo(userId: string): Promise<AuthUserInfo> {
  const userResult = await authPool.query(
    `SELECT u.USER_ID as user_id, u.USER_ACNT as user_acnt, u.USER_NM as user_nm,
            u.RNKP_NM as rnkp_nm, u.ORG_SN as org_sn,
            o.ORG_NM as org_nm, o.ORG_ABBR_NM as org_abbr_nm
     FROM AUTH_USER u
     LEFT JOIN AUTH_ORG o ON u.ORG_SN = o.ORG_SN
     WHERE u.USER_ID = $1`,
    [userId]
  )

  if (userResult.rows.length === 0) {
    throw new AuthError('사용자를 찾을 수 없습니다.', 404)
  }

  const row = userResult.rows[0]

  // 역할 조회 (ROLE_SN + ROLE_CD)
  const rolesResult = await authPool.query(
    `SELECT r.ROLE_SN as role_sn, r.ROLE_CD as role_cd
     FROM AUTH_USER_ROLE ur
     JOIN AUTH_ROLE r ON ur.ROLE_SN = r.ROLE_SN
     WHERE ur.USER_ID = $1`,
    [userId]
  )

  const roles = rolesResult.rows.map((r: { role_cd: string }) => r.role_cd)
  const roleSns = rolesResult.rows.map((r: { role_sn: number }) => r.role_sn)

  // 트리 기반 resolved permissions (리소스 × 오퍼레이션)
  let permissions: Record<string, string[]>
  try {
    const treeNodes = await getPermTreeNodes()

    if (treeNodes.length > 0) {
      // AUTH_PERM_TREE가 존재 → 트리 기반 resolve
      const explicitPermsResult = await authPool.query(
        `SELECT ROLE_SN as role_sn, RSRC_CD as rsrc_cd, OPER_CD as oper_cd, GRANT_YN as grant_yn
         FROM AUTH_PERM WHERE ROLE_SN = ANY($1)`,
        [roleSns]
      )

      const explicitPermsPerRole = new Map<number, Map<string, boolean>>()
      for (const sn of roleSns) {
        explicitPermsPerRole.set(sn, new Map())
      }
      for (const p of explicitPermsResult.rows) {
        const roleMap = explicitPermsPerRole.get(p.role_sn)
        if (roleMap) {
          const key = makePermKey(p.rsrc_cd, p.oper_cd)
          roleMap.set(key, p.grant_yn === 'Y')
        }
      }

      const granted = resolvePermissions(treeNodes, explicitPermsPerRole)
      permissions = grantedSetToRecord(granted)
    } else {
      // AUTH_PERM_TREE 미존재 (마이그레이션 전) → 기존 플랫 방식 fallback
      permissions = await flatPermissionsFallback(userId)
    }
  } catch {
    // AUTH_PERM_TREE 테이블 미존재 시 fallback
    try {
      permissions = await flatPermissionsFallback(userId)
    } catch {
      console.error('[auth] 권한 조회 fallback 실패, 빈 권한 반환')
      permissions = {}
    }
  }

  return {
    id: row.user_id,
    account: row.user_acnt,
    name: row.user_nm,
    rank: row.rnkp_nm,
    org: row.org_sn ? { sn: row.org_sn, name: row.org_nm, abbr: row.org_abbr_nm } : null,
    roles,
    permissions,
  }
}

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, SALT_ROUNDS)
}

async function recordLoginHistory(
  userId: string,
  ipAddr: string,
  userAgent: string,
  success: boolean
): Promise<void> {
  await authPool.query(
    `INSERT INTO AUTH_LOGIN_HIST (USER_ID, IP_ADDR, USER_AGENT, SUCCESS_YN)
     VALUES ($1, $2, $3, $4)`,
    [userId, ipAddr, userAgent?.substring(0, 500), success ? 'Y' : 'N']
  )
}

export class AuthError extends Error {
  status: number
  constructor(message: string, status: number) {
    super(message)
    this.status = status
    this.name = 'AuthError'
  }
}
