require "sequel"
require "sinatra"
require 'newrelic_rpm'
require "slim"
require "pry"
require "envyable"
require "hashids"
require "set"

Envyable.load("config/env.yaml", ENV['RACK_ENV'])

if ENV['RACK_ENV'] != "test"
  DB = Sequel.connect(:adapter=>'postgres', :host=>ENV['SV_HOST'], :database=>ENV['SV_DB'], :user=>ENV['SV_USER'], :password=>ENV['SV_PASSWORD'], :sslmode => "require")
end

class Schueler < Sequel::Model(:schueler)
  one_to_many :sprecher_stimmen, :key => :sprecher_id, :class => :Sprecher
  one_to_many :schuko_stimmen, :key => :schuko_id, :class => :Schuko
end
class Lehrer < Sequel::Model(:lehrer)
  one_to_many :lehrer_stimmen, :key => :lehrer_id, :class => :Verbindungslehrer
end
class Info < Sequel::Model(:infos);end
class Sprecher < Sequel::Model(:sprecher);end
class Schuko < Sequel::Model(:schuko);end
class Verbindungslehrer < Sequel::Model(:verbindungslehrer);end

class SV < Sinatra::Application
  configure do
    enable :sessions
    set :session_secret, (ENV['SV_SESSION_SECRET'] || 'your_secret')
    Slim::Engine.set_options pretty: true
    enable :static
    set :public_folder, File.dirname(__FILE__) + '/public'
  end

  configure :production do
    newrelic_ignore '/ping'
  end

  helpers do
    def protected!
      return if authorized?
      headers['WWW-Authenticate'] = 'Basic realm="Bitte anmelden"'
      halt 401, "Not authorized\n"
    end

    def authorized?
      @auth ||=  Rack::Auth::Basic::Request.new(request.env)
      @auth.provided? and @auth.basic? and @auth.credentials and @auth.credentials == [ENV['SV_BASIC_AUTH_USER'], ENV['SV_BASIC_AUTH_PASSWORD']]
    end

    def hashids
      hashids = Hashids.new(ENV['SV_SESSION_SECRET'], 5, "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789")
    end
  end

  error do
    'Es ist ein Fehler aufgetreten. Sollte das nochmal passieren, schreiben Sie mir doch bitte eine eMail: bk@hmt.im'
  end

  before do
    if session[:flash] && session[:flash][0] == 0
      logger.info session[:flash][1]
    end
  end

  get '/' do
    infos = Info.all
    slim :home, :locals => {:infos => infos}
  end

  get '/ping' do
    halt 200
  end

  post "/wahl" do
    if Time.now > time_for('Sep 30, 2016, 18:00')
      session[:flash] = [0, "Sie können sich nicht mehr aufstellen lassen, die Wahl hat bereits begonnen"]
      redirect back
    end
    schueler = Schueler.where(:hashid => params[:hashid]).first
    if schueler.update_fields params, [:info, :email, :schuko, :sprecher, :nachname]
      session[:flash] = [1, "Ihre Eingabe wurde gespeichert"]
    else
      session[:flash] = [0, "Es gab einen Fehler beim Speichern"]
    end
    redirect back
  end

  post '/info' do
    protected!
    if DB[:infos].insert(params)
      session[:flash] = [1, "Die Info wurde gespeichert"]
    else
      session[:flash] = [0, "Es gab einen Fehler beim Speichern der Info"]
    end
    redirect to("/")
  end

  get '/info_loeschen/:id' do |id|
    protected!
    Info.where(:id => id).first.delete
    redirect back
  end

  get '/info' do
    protected!
    infos = Info.all
    slim :info, :locals => {:infos => infos}
  end

  get '/ergebnis' do
    protected!
    sprecher, schuko, lehrer, waehler = [], [], [], 0
    DB.transaction do
      sprecher = Schueler.where(:sprecher => true).all
      schuko = Schueler.where(:schuko => true).all
      lehrer = Lehrer.all
      waehler = Schueler.where(:gewaehlt => true).count
    end
    slim :ergebnis, :locals => {:sprecher => sprecher, :schuko => schuko, :lehrer => lehrer, :waehler => waehler}
  end

  post "/hashid" do
    redirect to("/#{params[:hashid]}")
  end

  get "/:hashid" do
    schueler = Schueler.where(:hashid => params[:hashid]).first
    if schueler.nil?
      halt 404, "Adresse richtig abgetippt? Eventuell geht es mit dem QR-Code am Telefon leichter?"
    end
    lehrer = Lehrer.all
    slim :hashid, :locals => {:schueler => schueler, :lehrer => lehrer}
  end

  post "/:hashid" do
    if Time.now > time_for('Oct 3, 2016, 18:00')
      session[:flash] = [0, "Der Wahlzeitraum ist seit 3. Oktober 18:00 Uhr abgelaufen."]
      redirect back
    elsif Time.now < time_for('Sep 30, 2016, 18:00')
      session[:flash] = [0, "Der Wahlzeitraum beginnt erst am 30. September um 18:00 Uhr."]
      redirect back
    end

    schueler = Schueler.where(:hashid => params[:hashid]).first
    if schueler.nil?
      halt 404, "Hier stimmt etwas nicht mit Ihrem persönlichen Code"
    end
    if schueler.gewaehlt
      session[:flash] = [0, "Sie haben schon gewählt"]
      redirect back
    end
    sprecher, schuko, lehrer = params[:sprecher], params[:schuko], params[:lehrer]
    if sprecher && sprecher.count > 2
      session[:flash] = [0, "Zu viele Sprecher gewählt (max. 2)"]
      redirect back
    elsif schuko && schuko.count > 8
      session[:flash] = [0, "Zu viele Vertreter für die Schulkonferenz gewählt (max. 8)"]
      redirect back
    elsif lehrer && lehrer.count > 2
      session[:flash] = [0, "Zu viele Vertrauenslehrer gewählt (max. 2)"]
      redirect back
    end
    stimm_ids = []
    sprecher_ids, schuko_ids, lehrer_ids =[], [], []
    stimmabgabe = DB.transaction do
      sprecher && sprecher.to_set.each do |s|
        if Schueler[s].sprecher
          sprecher_ids << DB[:sprecher].insert(:sprecher_id => s)
        else
          raise Sequel::Rollback
          session[:flash] = [0, "Es wurde ein nichtaufgestellter Sprecher gewählt"]
          redirect back
        end
      end
      schuko && schuko.to_set.each do |s|
        if Schueler[s].schuko
          schuko_ids << DB[:schuko].insert(:schuko_id => s)
        else
          raise Sequel::Rollback
          session[:flash] = [0, "Es wurde ein nichtaufgestellter Vertreter zur Schulkonferenz gewählt"]
          redirect back
        end
      end
      lehrer && lehrer.to_set.each do |l|
        lehrer_ids << DB[:verbindungslehrer].insert(:lehrer_id => l)
      end
      schueler.update(:gewaehlt => true)
    end
    stimm_ids << sprecher_ids << schuko_ids << lehrer_ids
    stimm_hash = stimm_ids.map do |s_id|
      return "" if s_id.nil?
      hashids.encode s_id
    end
    if stimmabgabe
      session[:flash] = [1, "Ihre Wahl wurde gespeichert"]
      session[:stimm_hash] = stimm_hash.join("l")
    else
      session[:flash] = [0, "Es gab einen Fehler bei der Speicherung der Wahlergebnisse"]
    end
    redirect back
  end
end