# frozen_string_literal: true

require 'faye/redis'
require 'faye/websocket'
require 'net/http'
require 'rack/cors'
require 'sidekiq/web'
require 'sidekiq/pro/web'
require 'twitch/audit'
require_relative '../config/constants'

module Matterhorn
  class Web < Matterhorn::Roda
    configure :development do
      if defined? BetterErrors
        use BetterErrors::Middleware
        BetterErrors::Middleware.allow_ip! '0.0.0.0/0'
        BetterErrors.application_root = APP_ROOT
      end
    end

    use Rack::Cors do
      allow do
        origins /^https?:\/\/localhost(:\d+)?$/, 'https://savant.csi-tools-shadow.tech', 'https://savant.xarth.tv'

        resource '/*', methods: :any, headers: :any, max_age: 600
      end
    end

    use Rack::Session::Cookie, key: SESSION_KEY, secret: MATTERHORN_SECRET_KEY
    Sidekiq::Web.set :session_secret, MATTERHORN_SECRET_KEY
    Sidekiq::Web.set :sessions, key: SESSION_KEY

    plugin :csrf, header: 'X-CSRF-TOKEN', skip: ['POST:/hook/.*', 'POST:/_debug/.*', 'POST:/sidekiq/.*', 'POST:/faye', 'POST:/api/.*']

    log.info 'setting up faye server'
    log.info "faye redis url: redis://#{FAYE_REDIS_HOST}:#{FAYE_REDIS_PORT}/#{FAYE_REDIS_DB}"
    Faye::WebSocket.load_adapter 'puma'
    use Faye::RackAdapter, mount: '/faye', timeout: 30, engine: {
      type: Faye::Redis,
      host: FAYE_REDIS_HOST, port: FAYE_REDIS_PORT, database: FAYE_REDIS_DB
    } do |bayeux|
      bayeux.add_extension(Matterhorn::FayeSignMiddleware.new)
    end

    use Bouncer::Middleware

    Twitch::Audit::Middleware.configure do |config|
      config.dead_letter_bucket = AUDIT_S3_BUCKET
      config.service_name = 'matterhorn'
      config.sns_topic_arn = AUDIT_SNS_TOPIC
    end
    use Twitch::Audit::Middleware

    log.info 'configuring GitHub API endpoint'
    Octokit.configure do |c|
      c.api_endpoint = GITHUB_URL
    end

    route do |r|
      r.root do
        time_array = Time.now.to_a
        time_array[1] = (time_array[1] / 10) * 10
        t = Time.local(*time_array)
        "Now distributing #{(t + 3600).strftime('%l:%M %p')} to #{(t + 7200).strftime('%l:%M %p')} FASTPASS tickets"
      end

      r.on '_debug' do
        break unless api_admin? do |entry|
          entry.operation_name = 'Debug'
          entry.handler = "#{self.class}#route"
        end

        r.multi_route '_debug'
      end

      r.on 'api' do
        res['Content-Type'] = 'application/json'

        r.on 'v1' do
          r.multi_route 'api/v1'
        end
      end

      r.on 'hook' do
        r.multi_route 'webhook'
      end

      r.on 'sidekiq' do
        # expected to 404 if not authenticated
        break unless api_admin? do |entry|
          entry.operation_name = 'SidekiqAdmin'
          entry.handler = "#{self.class}#sidekiq"
        end
        r.run Sidekiq::Web
      end
    end

    def bouncer_session
      @bouncer_session ||= begin
        sess = Bouncer::Session.new(env)
        sess.token ||= Bouncer::Middleware.decode(req.cookies['bouncer_auth']) \
          if req.referer.nil? || req.referer.include?(SITE_URL) # fallback to cookie auth to allow direct access to sidekiq admin panel and debug routes
        sess
      end
    end

    def audit_log(&block)
      return unless File.extname(req.path).empty?
      Twitch::Audit::RequestContext.hydrate_entry(&block) if block_given?
    end

    def action_type_for_method
      return 'Write' if ['POST', 'PUT', 'DELETE'].include?(req.request_method)
      'Read'
    end

    def api_authenticated?(&block)
      audit_log(&block) if block_given?
      audit_log do |entry|
        entry.roles_allowed ||= ''
        entry.action_type ||= action_type_for_method
      end
      bouncer_session.valid?
    end

    def api_admin?(&block)
      audit_log(&block) if block_given?
      audit_log { |entry| entry.roles_allowed ||= allowed_roles(nil, type: :admin) }
      api_authenticated? && bouncer_session.include?(ROLES[:admin])
    end

    def queue_manager?(project, &block)
      audit_log(&block) if block_given?
      audit_log { |entry| entry.roles_allowed ||= allowed_roles(project, type: :queue_manager) }
      api_authenticated? && queue_manager_role?(project) || api_admin?
    end

    def allowed_roles(project = nil, type: nil)
      roles = [ROLES[:admin]]
      unless type.nil? || type == :admin
        PROJECTS[:platforms].each do |platform, projects|
          next unless projects.include?(project)

          roles << ROLES[type][platform] if ROLES[type][platform]
        end
      end
      roles.join(',')
    end

    def queue_manager_role?(project)
      PROJECTS[:platforms].each do |platform, projects|
        next unless projects.include?(project)
        return true if ROLES[:queue_manager][platform] && bouncer_session.include?(ROLES[:queue_manager][platform])
      end
      false
    end

    def body
      @body ||= req.body.read
    end

    def github
      @github ||= Octokit::Client.new(access_token: GITHUB_ACCESS_TOKEN)
    end
  end
end
