# frozen_string_literal: true

require 'indicina/logging'

require 'hashie'
require 'json'
require 'sequel'
require 'time'
require 'uri'

module Indicina
  class Test < Sequel::Model
    extend Logging

    unrestrict_primary_key

    plugin :lazy_attributes, :params

    many_to_one :suite, class: 'Indicina::TestSuite'
    one_to_many :runs, class: 'Indicina::TestRun'

    class << self
      def from_s3(key, suite)
        test_id = key.rpartition('/')[0].rpartition('/')[2]

        find_or_create(id: test_id) do |test|
          test.suite = suite

          obj = Indicina.s3_client.object(key)
          definition = JSON.parse(obj.get.body.read, symbolize_names: true)
          test.set_fields(definition, [:browser, :platform, :experience, :location, :mobile_browser, :url, :sample_length], missing: :skip)
          test.set params: definition[:test_params].to_json
          test.set platform: key.split('/')[3] if test.platform.nil? # compatibility

          test.update_from_s3
        end
      end
    end

    def update_from_s3
      info "Updating #{id} from s3 data..."

      update valid: false unless s3_exists?(filename: 'video.mp4')

      results = s3_read(filename: 'results.json')

      if results.nil?
        warn "Skipping #{id} as there is no results.json."
        return
      end

      results['data'].tap do |data|
        update bandwidth_down: data['bwDown'], bandwidth_up: data['bwUp'], latency: data['latency'], tested_at: Time.at(data['completed'])
        debug "Tested At: #{self.tested_at}, Json: #{data['completed']}"
      end

      median_run = results.dig('data', 'median', 'firstView', 'run') || -1
      results['data']['runs'].each_value do |data|
        data = data['firstView']

        if data['loadTime'].nil?
          warn "Skipping run #{data['run']} as it is missing `load_time`."
          next
        end

        TestRun.find_or_create(test: self, run: data['run']) do |run|
          run.set tested_at: Time.at(data['date']), is_median: data['run'] == median_run

          run.set first_byte: data['TTFB'], dom_loading: data['domLoading'], title_time: data['titleTime']
          run.set first_paint: data['firstPaint'], render: data['render'], dom_interactive: data['domInteractive']
          run.set load_time: data['loadTime'], fully_loaded: data['fullyLoaded'], last_visual_change: data['lastVisualChange']

          run.set dom_elements: data['domElements'], bytes_out: data['bytesOut'], bytes_in: data['bytesIn']
          run.set connections: data['connections'], requests: data['requests'].count, domain_names: data['domains'].count
          run.set responses_200: data['responses_200'], responses_404: data['responses_404'], responses_other: data['responses_other']
        end
      end

      self
    end

    # S3 Helpers
    def s3_exists?(filename:)
      Indicina.s3_client.object(File.join(storage_folder, filename)).exists?
    end

    def s3_read(filename: nil, s3_obj: nil, as: :hash)
      raise 'Reading from s3 require s either a `filename` OR `s3_obj`.' unless filename.nil? ^ s3_obj.nil?
      s3_obj = Indicina.s3_client.object(File.join(storage_folder, filename)) if s3_obj.nil?

      debug "Reading #{s3_obj.key} from s3..."

      body = s3_obj.get.body.read
      case as
        when :hash
          JSON.parse(body)
        when :mash
          Hashie::Mash.new(JSON.parse(body))
        else
          warn "Read does not know how to coerce #{s3_obj.key} into a #{as.name}"
      end
    rescue Aws::S3::Errors::NoSuchKey
      nil
    end

    def storage_folder
      return @storage_folder if instance_variable_defined?(:@storage_folder)

      parts = [
        experience,
        platform,
        location,
        mobile_browser || browser
      ]

      parts = [
        suite.storage_folder,
        parts.collect(&:to_s).collect(&:sanitize).collect(&:downcase),
        id.sanitize
      ]

      @storage_folder ||= File.join(parts, '/')
    end

    def json_url
      URI.join(CDN_ROOT, storage_folder, 'results.json').to_s
    end

    def video_url
      URI.join(CDN_ROOT, storage_folder, 'video.mp4').to_s
    end
  end
end