import express from 'express'
import cors from 'cors'
import jwt from 'jsonwebtoken'
import bcrypt from 'bcryptjs'
import { initDb } from './db.js'
import path from 'path'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const app = express()
app.use(cors())
app.use(express.json({ limit: '10mb' })) // Increase limit for saves

// Serve built React frontend
const distPath = path.join(__dirname, '..', 'dist')
app.use(express.static(distPath))

const JWT_SECRET = 'bach-hon-bang-secret-key-2026'

let db

function normalizeUsername(value) {
  return typeof value === 'string' ? value.trim() : ''
}

function validateRegisterPayload(username, password, confirmPassword) {
  if (!username || !password) {
    return 'Vui lòng nhập tài khoản và mật khẩu.'
  }

  if (username.length < 3) {
    return 'Tài khoản phải có ít nhất 3 ký tự.'
  }

  if (username.length > 24) {
    return 'Tài khoản chỉ được tối đa 24 ký tự.'
  }

  if (/\s/.test(username)) {
    return 'Tài khoản không được chứa khoảng trắng.'
  }

  if (password.length < 4) {
    return 'Mật khẩu phải có ít nhất 4 ký tự.'
  }

  if (confirmPassword !== undefined && password !== confirmPassword) {
    return 'Mật khẩu xác nhận không khớp.'
  }

  return ''
}

// Middleware for authentication
async function authenticate(req, res, next) {
  const authHeader = req.headers.authorization
  if (!authHeader) return res.status(401).json({ error: 'Missing token' })

  const token = authHeader.split(' ')[1]
  try {
    const payload = jwt.verify(token, JWT_SECRET)
    const user = await db.get('SELECT id, username, role FROM users WHERE id = ?', [payload.id])
    if (!user) return res.status(401).json({ error: 'User not found' })
    req.user = user
    next()
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' })
  }
}

function requireAdmin(req, res, next) {
  if (req.user.role !== 'ADMIN' && req.user.role !== 'BANG_CHU') {
    return res.status(403).json({ error: 'Requires Admin/BangChu role' })
  }
  next()
}

function requireManager(req, res, next) {
  if (req.user.role !== 'ADMIN' && req.user.role !== 'BANG_CHU' && req.user.role !== 'BANG_PHO') {
    return res.status(403).json({ error: 'Requires Admin/BangChu/BangPho role' })
  }
  next()
}

// =======================
// AUTH API
// =======================

app.post('/api/auth/register', async (req, res) => {
  const username = normalizeUsername(req.body?.username)
  const password = typeof req.body?.password === 'string' ? req.body.password : ''
  const validationError = validateRegisterPayload(username, password)

  if (validationError) {
    return res.status(400).json({ error: validationError })
  }
  
  try {
    const hash = await bcrypt.hash(password, 10)
    const result = await db.run("INSERT INTO users (username, password, role) VALUES (?, ?, 'MEMBER')", [username, hash])
    const token = jwt.sign({ id: result.lastID, role: 'MEMBER' }, JWT_SECRET)
    res.json({ token, user: { id: result.lastID, username, role: 'MEMBER' } })
  } catch (err) {
    if (err.message.includes('UNIQUE constraint failed')) {
      return res.status(400).json({ error: 'Tài khoản này đã tồn tại.' })
    }

    if (err.message.includes('SQLITE_READONLY')) {
      return res.status(503).json({
        error: 'Máy chủ chưa có quyền ghi dữ liệu. Hãy khởi động lại app sau khi deploy bản vá database mới.',
      })
    }

    res.status(500).json({ error: err.message })
  }
})

app.post('/api/auth/login', async (req, res) => {
  const username = normalizeUsername(req.body?.username)
  const password = typeof req.body?.password === 'string' ? req.body.password : ''

  if (!username || !password) {
    return res.status(400).json({ error: 'Vui lòng nhập tài khoản và mật khẩu.' })
  }

  const user = await db.get('SELECT * FROM users WHERE username = ?', [username])
  if (!user) return res.status(400).json({ error: 'Sai tài khoản hoặc mật khẩu.' })

  const valid = await bcrypt.compare(password, user.password)
  if (!valid) return res.status(400).json({ error: 'Sai tài khoản hoặc mật khẩu.' })

  const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET)
  res.json({ token, user: { id: user.id, username: user.username, role: user.role } })
})

app.get('/api/auth/me', authenticate, (req, res) => {
  res.json({ user: req.user })
})

app.get('/api/users', authenticate, requireManager, async (req, res) => {
  const users = await db.all('SELECT id, username, role FROM users')
  res.json(users)
})

app.post('/api/users/:id/role', authenticate, requireAdmin, async (req, res) => {
  const { role } = req.body
  const validRoles = ['ADMIN', 'BANG_CHU', 'BANG_PHO', 'MEMBER']
  if (!validRoles.includes(role)) return res.status(400).json({ error: 'Invalid role' })
  
  await db.run('UPDATE users SET role = ? WHERE id = ?', [role, req.params.id])
  res.json({ success: true })
})

// =======================
// STATE API
// =======================

app.get('/api/state', async (req, res) => {
  const stateRow = await db.get('SELECT * FROM state WHERE id = 1')
  const members = await db.all('SELECT * FROM members')
  const memberSkills = await db.all('SELECT * FROM member_skills')
  const skillLibrary = await db.all('SELECT * FROM skill_library')

  res.json({
    members,
    memberSkills,
    skillLibrary,
    teamGroups: JSON.parse(stateRow.teamGroups || '{}'),
    teamPositions: JSON.parse(stateRow.teamPositions || '{}'),
    registrationLocked: stateRow.registrationLocked === 1
  })
})

app.post('/api/state/member', authenticate, async (req, res) => {
  const { id, char_name, class_name, team_slot, type, has_item } = req.body
  
  if (id) {
    // Check permissions
    const existing = await db.get('SELECT * FROM members WHERE id = ?', [id])
    if (!existing) return res.status(404).json({ error: 'Member not found' })
    
    if (req.user.role === 'MEMBER' && existing.created_by !== req.user.id) {
      return res.status(403).json({ error: 'Bạn không có quyền sửa nhân vật của người khác.' })
    }

    await db.run(`
      UPDATE members 
      SET char_name = ?, class_name = ?, team_slot = ?, type = ?, has_item = ? 
      WHERE id = ?
    `, [char_name, class_name, team_slot, type, has_item ? 1 : 0, id])
  } else {
    // Insert new
    // Check if registration is locked for members
    const stateRow = await db.get('SELECT registrationLocked FROM state WHERE id = 1')
    if (stateRow.registrationLocked === 1 && req.user.role === 'MEMBER') {
       return res.status(403).json({ error: 'Đăng ký nhân vật hiện đang bị quản trị viên khóa.' })
    }

    try {
      await db.run(`
        INSERT INTO members (char_name, class_name, team_slot, type, has_item, created_by)
        VALUES (?, ?, ?, ?, ?, ?)
      `, [char_name, class_name, team_slot, type, has_item ? 1 : 0, req.user.id])
    } catch (err) {
       return res.status(400).json({ error: 'Tên nhân vật đã tồn tại.' })
    }
  }
  
  res.json({ success: true })
})

app.post('/api/state/member/move', authenticate, async (req, res) => {
  const { id, team_slot, type } = req.body
  const existing = await db.get('SELECT * FROM members WHERE id = ?', [id])
  if (!existing) return res.status(404).json({ error: 'Member not found' })
  
  if (req.user.role === 'MEMBER' && existing.created_by !== req.user.id) {
    return res.status(403).json({ error: 'Bạn không có quyền di chuyển nhân vật của người khác.' })
  }

  // Swap logic on frontend is complex, so we expect the frontend to send the move.
  // Actually, if frontend swaps, it sends two requests or one swap request.
  // Let's just update the target slot.
  await db.run('UPDATE members SET team_slot = ?, type = ? WHERE id = ?', [team_slot, type, id])
  res.json({ success: true })
})

app.delete('/api/state/member/:id', authenticate, async (req, res) => {
  const existing = await db.get('SELECT * FROM members WHERE id = ?', [req.params.id])
  if (!existing) return res.status(404).json({ error: 'Member not found' })
  
  if (req.user.role === 'MEMBER' && existing.created_by !== req.user.id) {
    return res.status(403).json({ error: 'Bạn không có quyền xóa nhân vật của người khác.' })
  }

  await db.run('DELETE FROM member_skills WHERE member_id = ?', [req.params.id])
  await db.run('DELETE FROM members WHERE id = ?', [req.params.id])
  res.json({ success: true })
})

// Full Sync (Used by Managers/Admins for restoring, saving settings, etc.)
app.post('/api/state/sync', authenticate, requireManager, async (req, res) => {
  const { members, memberSkills, skillLibrary, teamGroups, teamPositions, registrationLocked } = req.body

  await db.run('BEGIN TRANSACTION')
  try {
    // Sync Members
    if (members) {
      await db.run('DELETE FROM members')
      for (const m of members) {
        await db.run(`
          INSERT INTO members (id, char_name, class_name, team_slot, type, has_item, created_by, created_at)
          VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        `, [m.id, m.char_name, m.class_name, m.team_slot, m.type, m.has_item ? 1 : 0, m.created_by, m.created_at || new Date().toISOString()])
      }
    }

    // Sync Member Skills
    if (memberSkills) {
      await db.run('DELETE FROM member_skills')
      for (const ms of memberSkills) {
        await db.run(`
          INSERT INTO member_skills (id, member_id, skill_id, pos_x, pos_y)
          VALUES (?, ?, ?, ?, ?)
        `, [ms.id, ms.member_id, ms.skill_id, ms.pos_x, ms.pos_y])
      }
    }

    // Sync Skill Library
    if (skillLibrary) {
      await db.run('DELETE FROM skill_library')
      for (const sl of skillLibrary) {
        await db.run('INSERT INTO skill_library (id, url) VALUES (?, ?)', [sl.id, sl.url])
      }
    }

    // Sync Settings & Map
    if (teamGroups || teamPositions || registrationLocked !== undefined) {
      const lockVal = registrationLocked ? 1 : 0
      const groupsStr = JSON.stringify(teamGroups || {})
      const posStr = JSON.stringify(teamPositions || {})
      await db.run(`
        UPDATE state 
        SET registrationLocked = ?, teamGroups = ?, teamPositions = ? 
        WHERE id = 1
      `, [lockVal, groupsStr, posStr])
    }

    await db.run('COMMIT')
    res.json({ success: true })
  } catch (err) {
    await db.run('ROLLBACK')
    res.status(500).json({ error: err.message })
  }
})

app.post('/api/saves', authenticate, requireManager, async (req, res) => {
  const { slotIndex, state_json } = req.body
  // Since we only have 3 slots, slotIndex should be 0, 1, 2
  // Let's use id = slotIndex + 1 (1, 2, 3)
  const id = slotIndex + 1
  
  const existing = await db.get('SELECT id FROM save_slots WHERE id = ?', [id])
  if (existing) {
    await db.run('UPDATE save_slots SET state_json = ?, saved_at = CURRENT_TIMESTAMP WHERE id = ?', [state_json, id])
  } else {
    await db.run('INSERT INTO save_slots (id, state_json) VALUES (?, ?)', [id, state_json])
  }
  
  res.json({ success: true })
})

app.get('/api/saves', async (req, res) => {
  const slots = await db.all('SELECT id, saved_at, state_json FROM save_slots')
  res.json(slots)
})

const PORT = process.env.PORT || 3001

initDb().then((database) => {
  db = database

  // Catch-all: serve React app for any non-API route
  app.get('/{*path}', (req, res) => {
    res.sendFile(path.join(distPath, 'index.html'))
  })

  app.listen(PORT, () => {
    console.log(`Backend server running on http://localhost:${PORT}`)
  })
}).catch(console.error)

app.post('/api/state/skills', authenticate, async (req, res) => {
  const { memberId, skills } = req.body
  const existing = await db.get('SELECT * FROM members WHERE id = ?', [memberId])
  if (!existing) return res.status(404).json({ error: 'Member not found' })
  
  if (req.user.role === 'MEMBER' && existing.created_by !== req.user.id) {
    return res.status(403).json({ error: 'Bạn không có quyền sửa skill nhân vật này.' })
  }

  await db.run('BEGIN TRANSACTION')
  try {
    await db.run('DELETE FROM member_skills WHERE member_id = ?', [memberId])
    for (const s of skills) {
      await db.run(`
        INSERT INTO member_skills (member_id, skill_id, pos_x, pos_y)
        VALUES (?, ?, ?, ?)
      `, [memberId, s.skill_id, s.pos_x, s.pos_y])
    }
    await db.run('COMMIT')
    res.json({ success: true })
  } catch(err) {
    await db.run('ROLLBACK')
    res.status(500).json({ error: err.message })
  }
})
