authentication = require 'passport'
LocalStrategy = require('passport-local').Strategy
User = require '../users/User'
UserHandler = require '../users/user_handler'
LevelSession = require '../levels/sessions/LevelSession'
config = require '../../server_config'
errors = require '../commons/errors'
languages = require '../routes/languages'
sendwithus = require '../sendwithus'
log = require 'winston'

module.exports.setup = (app) ->
  authentication.serializeUser((user, done) -> done(null, user._id))
  authentication.deserializeUser((id, done) ->
    User.findById(id, (err, user) -> done(err, user)))

  authentication.use(new LocalStrategy(
    (username, password, done) ->

      # kind of a hacky way to make it possible for iPads to 'log in' with their unique device id
      if username.length is 36 and '@' not in username # must be an identifier for vendor
        q = { iosIdentifierForVendor: username }
      else
        q = { emailLower: username.toLowerCase() }

      User.findOne(q).exec((err, user) ->
        return done(err) if err
        return done(null, false, {message: 'not found', property: 'email'}) if not user
        passwordReset = (user.get('passwordReset') or '').toLowerCase()
        if passwordReset and password.toLowerCase() is passwordReset
          User.update {_id: user.get('_id')}, {passwordReset: ''}, {}, ->
          return done(null, user)

        hash = User.hashPassword(password)
        unless user.get('passwordHash') is hash
          return done(null, false, {message: 'is wrong', property: 'password'})
        return done(null, user)
      )
  ))

  app.post '/auth/spy', (req, res, next) ->
    if req?.user?.isAdmin()
      target = req.body.nameOrEmailLower
      return errors.badInput res, 'Specify a username or email to espionage.' unless target
      query = $or: [{nameLower: target}, {emailLower: target}]
      User.findOne query, (err, user) ->
        if err? then return errors.serverError res, 'There was an error finding the specified user'
        unless user then return errors.badInput res, 'The specified user couldn\'t be found'
        req.logIn user, (err) ->
          if err? then return errors.serverError res, 'There was an error logging in with the specified user'
          res.send(UserHandler.formatEntity(req, user))
          return res.end()
    else
      return errors.unauthorized res, 'You must be an admin to enter espionage mode'

  app.post('/auth/login', (req, res, next) ->
    authentication.authenticate('local', (err, user, info) ->
      return next(err) if err
      if not user
        return errors.unauthorized(res, [{message: info.message, property: info.property}])

      req.logIn(user, (err) ->
        return next(err) if (err)
        activity = req.user.trackActivity 'login', 1
        user.update {activity: activity}, (err) ->
          return next(err) if (err)
          res.send(UserHandler.formatEntity(req, req.user))
          return res.end()
      )
    )(req, res, next)
  )

  app.get('/auth/whoami', (req, res) ->
    if req.user
      sendSelf(req, res)
    else
      user = makeNewUser(req)
      makeNext = (req, res) -> -> sendSelf(req, res)
      next = makeNext(req, res)
      loginUser(req, res, user, false, next)
  )

  sendSelf = (req, res) ->
    res.setHeader('Content-Type', 'text/json')
    if req.query.callback
      res.jsonp UserHandler.formatEntity(req, req.user, true)
    else
      res.send UserHandler.formatEntity(req, req.user, false)
    res.end()

  app.post('/auth/logout', (req, res) ->
    req.logout()
    res.end()
  )

  app.post('/auth/reset', (req, res) ->
    unless req.body.email
      return errors.badInput(res, [{message: 'Need an email specified.', property: 'email'}])

    User.findOne({emailLower: req.body.email.toLowerCase()}).exec((err, user) ->
      if not user
        return errors.notFound(res, [{message: 'not found', property: 'email'}])

      user.set('passwordReset', Math.random().toString(36).slice(2, 7).toUpperCase())
      user.save (err) =>
        return errors.serverError(res) if err
        unless config.unittest
          context =
            email_id: sendwithus.templates.generic_email
            recipient:
              address: req.body.email
            email_data:
              subject: 'CodeCombat Recovery Password'
              title: 'Recovery Password'
              content: "<p>Your CodeCombat recovery password for email #{req.body.email} is: #{user.get('passwordReset')}</p><p>Log in at <a href=\"http://codecombat.com/account/settings\">http://codecombat.com/account/settings</a> and change it.</p><p>Hope this helps!</p>"
          sendwithus.api.send context, (err, result) ->
            if err
              console.error "Error sending password reset email: #{err.message or err}"
              return errors.serverError(res) if err
            else
              return res.end()
        else
          console.log 'password is', user.get('passwordReset')
          res.send user.get('passwordReset')
          return res.end()
    )
  )

  app.get '/auth/unsubscribe', (req, res) ->
    req.query.email = decodeURIComponent(req.query.email)
    email = req.query.email
    unless req.query.email
      return errors.badInput res, 'No email provided to unsubscribe.'

    if req.query.session
      # Unsubscribe from just one session's notifications instead.
      return LevelSession.findOne({_id: req.query.session}).exec (err, session) ->
        return errors.serverError res, 'Could not unsubscribe: #{req.query.session}, #{req.query.email}: #{err}' if err
        session.set 'unsubscribed', true
        session.save (err) ->
          return errors.serverError res, 'Database failure.' if err
          res.send "Unsubscribed #{req.query.email} from CodeCombat emails for #{session.get('levelName')} #{session.get('team')} ladder updates. Sorry to see you go! <p><a href='/play/ladder/#{session.levelID}#my-matches'>Ladder preferences</a></p>"
          res.end()

    User.findOne({emailLower: req.query.email.toLowerCase()}).exec (err, user) ->
      if not user
        return errors.notFound res, "No user found with email '#{req.query.email}'"

      emails = _.clone(user.get('emails')) or {}
      msg = ''

      if req.query.recruitNotes
        emails.recruitNotes ?= {}
        emails.recruitNotes.enabled = false
        msg = "Unsubscribed #{req.query.email} from recruiting emails."
      else if req.query.employerNotes
        emails.employerNotes ?= {}
        emails.employerNotes.enabled = false

        msg = "Unsubscribed #{req.query.email} from employer emails."
      else
        msg = "Unsubscribed #{req.query.email} from all CodeCombat emails. Sorry to see you go!"
        emailSettings.enabled = false for emailSettings in _.values(emails)
        emails.generalNews ?= {}
        emails.generalNews.enabled = false
        emails.anyNotes ?= {}
        emails.anyNotes.enabled = false

      user.update {$set: {emails: emails}}, {}, =>
        return errors.serverError res, 'Database failure.' if err
        res.send msg + '<p><a href="/account/settings">Account settings</a></p>'
        res.end()

  app.get '/auth/name*', (req, res) ->
    parts = req.path.split '/'
    originalName = decodeURI parts[3]
    return errors.badInput res, 'No name provided.' unless parts.length > 3 and originalName? and originalName isnt ''
    return errors.notFound res if parts.length isnt 4

    User.unconflictName originalName, (err, name) ->
      return errors.serverError res, err if err
      response = name: name
      if originalName is name
        res.send 200, response
      else
        errors.conflict res, response


module.exports.loginUser = loginUser = (req, res, user, send=true, next=null) ->
  user.save((err) ->
    return errors.serverError res, err if err?

    req.logIn(user, (err) ->
      return errors.serverError res, err if err?
      return res.send(user) and res.end() if send
      next() if next
    )
  )

module.exports.idCounter = 0

module.exports.makeNewUser = makeNewUser = (req) ->
  user = new User({anonymous: true})
  if global.testing
    # allows tests some control over user id creation
    newID = _.pad((module.exports.idCounter++).toString(16), 24, '0')
    user.set('_id', newID)
  user.set 'testGroupNumber', Math.floor(Math.random() * 256)  # also in app/core/auth
  lang = languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
  user.set 'preferredLanguage', lang if lang[...2] isnt 'en'
  user.set 'preferredLanguage', 'pt-BR' if not user.get('preferredLanguage') and /br\.codecombat\.com/.test(req.get('host'))
  user.set 'preferredLanguage', 'zh-HANS' if not user.get('preferredLanguage') and /cn\.codecombat\.com/.test(req.get('host'))
  user.set 'lastIP', (req.headers['x-forwarded-for'] or req.connection.remoteAddress)?.split(/,? /)[0]
  user.set 'country', req.country if req.country
  #log.info "making new user #{user.get('_id')} with language #{user.get('preferredLanguage')} of #{req.acceptedLanguages} and country #{req.country} on #{if config.tokyo then 'Tokyo' else (if config.saoPaulo then 'Brazil' else 'US')} server and lastIP #{user.get('lastIP')}."
  user