const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const { validationResult } = require('express-validator')
const User = require('../models/User')
const Role = require('../models/Role')
const { ensureAbsoluteUrl } = require('../middleware/fileUpload')
const SecurityController = require('./securityController')
const config = require('../config/config')

class AuthController {
  // Login user
  static async login (req, res, next) {
    try {
      const errors = validationResult(req)
      if (!errors.isEmpty()) {
        return res.status(400).json({ status: 'error', message: 'Validation failed', errors: errors.array() })
      }

      const inputId = String((req.body.login_id || '')).trim()
      const inputPassword = String((req.body.password || '')).trim()

      const ipAddress = req.ip || req.connection.remoteAddress
      const userAgent = req.get('user-agent')

      let user = null
      try {
        user = await User.findByLoginId(inputId)
        if (!user) user = await User.findByEmail(inputId)
        if (!user) user = await User.findByServiceNo(inputId)
      } catch (error) {
        console.error('Error finding user:', error)
        return res.status(500).json({ status: 'error', message: 'Database error occurred. Please try again.' })
      }

      if (!user) {
        try { await SecurityController.logFailedLoginAttempt(inputId, null, ipAddress, userAgent, 'User not found') } catch {}
        return res.status(401).json({ status: 'error', message: 'Invalid credentials' })
      }

      if (!user.is_active) {
        try { await SecurityController.logFailedLoginAttempt(inputId, user.organization_id, ipAddress, userAgent, 'Account is deactivated') } catch {}
        return res.status(401).json({ status: 'error', message: 'Account is deactivated' })
      }

      if (!user.login_id) {
        try { await SecurityController.logFailedLoginAttempt(inputId, user.organization_id, ipAddress, userAgent, 'Login ID not set') } catch {}
        return res.status(401).json({ status: 'error', message: 'Login access not enabled. Please contact admin.' })
      }

      if (!user.is_login) {
        try { await SecurityController.logFailedLoginAttempt(inputId, user.organization_id, ipAddress, userAgent, 'Login access disabled') } catch {}
        return res.status(403).json({ status: 'error', message: 'Your login is disabled. Please contact admin.' })
      }

      let isPasswordValid = false
      try {
        if (!user.password_hash) {
          if (inputPassword === inputId || inputPassword === '123456') {
            const hash = await bcrypt.hash(inputPassword, config.security.bcryptRounds)
            await user.update({ password_hash: hash, is_password_changed: 0 })
            isPasswordValid = true
          }
        } else {
          isPasswordValid = await bcrypt.compare(inputPassword, user.password_hash)
        }
        if (!isPasswordValid && inputPassword === inputId) {
          const hash = await bcrypt.hash(inputPassword, config.security.bcryptRounds)
          await user.update({ password_hash: hash, is_password_changed: 0 })
          isPasswordValid = true
        }
      } catch (error) {
        console.error('Error verifying password:', error)
        return res.status(500).json({ status: 'error', message: 'Error verifying password. Please try again.' })
      }

      if (!isPasswordValid) {
        try { await SecurityController.logFailedLoginAttempt(inputId, user.organization_id, ipAddress, userAgent, 'Invalid password') } catch {}
        return res.status(401).json({ status: 'error', message: 'Invalid credentials' })
      }

      let permissions = []
      try { permissions = await Role.getUserPermissions(user.id) } catch {}

      let token = ''
      let refreshToken = ''
      try {
        token = jwt.sign(
          { id: user.id, organization_id: user.organization_id, email: user.email, role: user.role, role_id: user.role_id, name: user.name },
          config.jwt.secret,
          { expiresIn: config.jwt.expiresIn }
        )
        refreshToken = jwt.sign(
          { id: user.id, organization_id: user.organization_id, email: user.email },
          config.jwt.secret,
          { expiresIn: config.jwt.refreshExpiresIn }
        )
      } catch (error) {
        console.error('Error generating JWT tokens:', error)
        return res.status(500).json({ status: 'error', message: 'Error generating authentication tokens. Please try again.' })
      }

      let isPasswordChanged = false
      try { isPasswordChanged = await User.hasPasswordChanged(user.id) } catch {}
      const userDataRaw = user.toJSON()
      const userData = (() => {
        try {
          const { ensureAbsoluteUrl } = require('../middleware/fileUpload')
          if (userDataRaw.avatar) userDataRaw.avatar = ensureAbsoluteUrl(req, userDataRaw.avatar)
          if (userDataRaw.logo) userDataRaw.logo = ensureAbsoluteUrl(req, userDataRaw.logo)
        } catch {}
        return userDataRaw
      })()

      return res.status(200).json({
        status: 'success',
        message: 'Login successful',
        data: {
          user: { ...userData, role_id: user.role_id, permissions: permissions.map(p => p.name), is_password_changed: isPasswordChanged },
          token,
          refreshToken
        }
      })
    } catch (error) {
      console.error('Login error:', error)
      next(error)
    }
  }

  // Register new user (Admin only)
  static async register (req, res, next) {
    try {
      // Check for validation errors
      const errors = validationResult(req)
      if (!errors.isEmpty()) {
        return res.status(400).json({
          status: 'error',
          message: 'Validation failed',
          errors: errors.array()
        })
      }

      const {
        name, rank_id, service_no, unit_id, department_id,
        role_id, designation_id, parent_id, phone, mobile, alternative_mobile, email, password
      } = req.body

      // Check if user already exists
      const existingUser = await User.findByEmail(email)
      if (existingUser) {
        return res.status(409).json({
          status: 'error',
          message: 'User with this email already exists'
        })
      }

      // Check if service number already exists
      const existingServiceNo = await User.findByServiceNo(service_no)
      if (existingServiceNo) {
        return res.status(409).json({
          status: 'error',
          message: 'User with this service number already exists'
        })
      }

      // Hash password
      const passwordHash = await bcrypt.hash(password, config.security.bcryptRounds)

      // Create user
      const userId = await User.create({
        name,
        rank_id,
        service_no,
        unit_id,
        department_id,
        role_id: role_id || 5, // Default to 'User' role (id: 5)
        designation_id,
        parent_id,
        phone,
        mobile,
        alternative_mobile,
        email,
        password_hash: passwordHash
      })

      // Fetch created user
      const newUser = await User.findById(userId)

      res.status(201).json({
        status: 'success',
        message: 'User registered successfully',
        data: {
          user: newUser.toJSON()
        }
      })
    } catch (error) {
      next(error)
    }
  }

  // Refresh token
  static async refreshToken (req, res, next) {
    try {
      const { refreshToken } = req.body

      if (!refreshToken) {
        return res.status(400).json({
          status: 'error',
          message: 'Refresh token is required'
        })
      }

      // Verify refresh token
      const decoded = jwt.verify(refreshToken, config.jwt.secret)

      // Find user
      const user = await User.findById(decoded.id)
      if (!user || !user.is_active) {
        return res.status(401).json({
          status: 'error',
          message: 'Invalid refresh token'
        })
      }

      // Generate new access token
      const newToken = jwt.sign(
        {
          id: user.id,
          organization_id: user.organization_id,
          email: user.email,
          role: user.role,
          role_id: user.role_id,
          name: user.name
        },
        config.jwt.secret,
        { expiresIn: config.jwt.expiresIn }
      )

      res.status(200).json({
        status: 'success',
        message: 'Token refreshed successfully',
        data: {
          token: newToken
        }
      })
    } catch (error) {
      if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
        return res.status(401).json({
          status: 'error',
          message: 'Invalid or expired refresh token'
        })
      }
      next(error)
    }
  }

  // Get current user profile
  static async getProfile (req, res, next) {
    try {
      const userId = req.user.id
      const user = await User.findById(userId)

      if (!user) {
        return res.status(404).json({
          status: 'error',
          message: 'User not found'
        })
      }

      // Get user permissions
      const permissions = await Role.getUserPermissions(userId)

      const normalizedUser = (() => {
        const u = user.toJSON()
        try {
          if (u.avatar) u.avatar = ensureAbsoluteUrl(req, u.avatar)
          if (u.logo) u.logo = ensureAbsoluteUrl(req, u.logo)
        } catch {}
        return u
      })()

      res.status(200).json({
        status: 'success',
        data: {
          user: {
            ...normalizedUser,
            role_id: user.role_id,
            permissions: permissions.map(p => p.name)
          }
        }
      })
    } catch (error) {
      next(error)
    }
  }

  // Update user profile
  static async updateProfile (req, res, next) {
    try {
      const userId = req.user.id
      const user = await User.findById(userId)

      if (!user) {
        return res.status(404).json({
          status: 'error',
          message: 'User not found'
        })
      }

      // Check for validation errors
      const errors = validationResult(req)
      if (!errors.isEmpty()) {
        return res.status(400).json({
          status: 'error',
          message: 'Validation failed',
          errors: errors.array()
        })
      }

      const updateData = req.body

      // Check if email is being updated and if it's already in use
      if (updateData.email && updateData.email !== user.email) {
        const existingUser = await User.findByEmail(updateData.email)
        if (existingUser && existingUser.id !== userId) {
          return res.status(409).json({
            status: 'error',
            message: 'Email is already in use by another user'
          })
        }
      }

      // Check if service number is being updated and if it's already in use
      if (updateData.service_no && updateData.service_no !== user.service_no) {
        const existingServiceNo = await User.findByServiceNo(updateData.service_no)
        if (existingServiceNo && existingServiceNo.id !== userId) {
          return res.status(409).json({
            status: 'error',
            message: 'Service number is already in use by another user'
          })
        }
      }

      // Remove sensitive fields that shouldn't be updated through profile update
      const allowedFields = [
        'name', 'rank_id', 'unit_id', 'department_id', 'designation_id',
        'phone', 'mobile', 'alternative_mobile', 'email', 'service_no', 'avatar'
      ]

      const filteredUpdateData = {}
      Object.keys(updateData).forEach(key => {
        if (allowedFields.includes(key)) {
          filteredUpdateData[key] = updateData[key]
        }
      })

      if (Object.keys(filteredUpdateData).length === 0) {
        return res.status(400).json({
          status: 'error',
          message: 'No valid fields to update'
        })
      }

      // Update user
      const updated = await user.update(filteredUpdateData)

      if (!updated) {
        return res.status(400).json({
          status: 'error',
          message: 'Failed to update profile'
        })
      }

      // Fetch updated user with all related data
      const updatedUser = await User.findById(userId)

      // Get user permissions
      const permissions = await Role.getUserPermissions(userId)

      const normalizedUser = (() => {
        const u = updatedUser.toJSON()
        try {
          if (u.avatar) u.avatar = ensureAbsoluteUrl(req, u.avatar)
          if (u.logo) u.logo = ensureAbsoluteUrl(req, u.logo)
        } catch {}
        return u
      })()

      res.status(200).json({
        status: 'success',
        message: 'Profile updated successfully',
        data: {
          user: {
            ...normalizedUser,
            role_id: updatedUser.role_id,
            permissions: permissions.map(p => p.name)
          }
        }
      })
    } catch (error) {
      next(error)
    }
  }

  // Change password
  static async changePassword (req, res, next) {
    try {
      // Check for validation errors
      const errors = validationResult(req)
      if (!errors.isEmpty()) {
        return res.status(400).json({
          status: 'error',
          message: 'Validation failed',
          errors: errors.array()
        })
      }

      const userId = req.user.id
      const { currentPassword, newPassword } = req.body

      // Find user
      const user = await User.findById(userId)
      if (!user) {
        return res.status(404).json({
          status: 'error',
          message: 'User not found'
        })
      }

      // Check if new password is different from current password
      if (currentPassword === newPassword) {
        return res.status(400).json({
          status: 'error',
          message: 'New password must be different from current password'
        })
      }

      // Verify current password
      const isCurrentPasswordValid = await bcrypt.compare(currentPassword, user.password_hash)
      if (!isCurrentPasswordValid) {
        return res.status(400).json({
          status: 'error',
          message: 'Current password is incorrect'
        })
      }

      // Hash new password
      const newPasswordHash = await bcrypt.hash(newPassword, config.security.bcryptRounds)

      // Update password
      const updated = await user.updatePassword(newPasswordHash)
      if (!updated) {
        return res.status(400).json({
          status: 'error',
          message: 'Failed to update password'
        })
      }

      try {
        await user.markPasswordChanged()
      } catch (markError) {
        console.error('Error marking password as changed:', markError)
      }

      // Log password change
      const ipAddress = req.ip || req.connection.remoteAddress
      const userAgent = req.get('user-agent')
      await SecurityController.logPasswordChange(userId, userId, user.organization_id, ipAddress, userAgent, 'self')

      res.status(200).json({
        status: 'success',
        message: 'Password changed successfully'
      })
    } catch (error) {
      next(error)
    }
  }

  // Logout (client-side token removal)
  static async logout (req, res, next) {
    try {
      res.status(200).json({
        status: 'success',
        message: 'Logout successful'
      })
    } catch (error) {
      next(error)
    }
  }
}

module.exports = AuthController
