require "minitest/autorun"
require "minitest/reporters"
Minitest::Reporters.use!


module WebserverHelper

  def message_with_context(message, context = nil)
    if context.nil? || context.empty?
      message
    else
      message + " #{context}"
    end
  end

  def webserver_env
    webserver_env = ENV['WEBSERVER_ENV']
    webserver_env = "production" if webserver_env.nil? || webserver_env.empty?
    if %w(production staging development).include?(webserver_env)
      webserver_env
    else
      fail ArgumentError, "Environnement #{webserver_env} invalide"
    end
  end

  def domain
    case webserver_env
    when "production"
      "www.example.com"
    when "staging"
      "www-staging.example.com"
    when "development"
      "local.example.com"
    else
      fail ArgumentError, "Domaine indéterminé"
    end
  end

  def base_url
    "https://#{domain}"
  end

  def internal_url?(url)
    URI(url).host[/example.com\Z/]
  end

  def agent
    @agent ||= Mechanize.new
  end

  def page_and_doc(url)
    page = agent.get(url)
    doc = Nokogiri::HTML(page.body)

    if block_given?
      yield(page, doc)
    else
      [page, doc]
    end
  end

  def on_page(path = "/", &block)
    uri = URI.join(base_url, path)
    page, doc = page_and_doc(uri)

    yield(page, doc)
  end

  def on_home_page(&block)
    on_page("/", &block)
  end

  def on_pages(pages = [], &block)
    pages.each do |page|
      on_page(page, &block)
    end
  end

  # Custom assertions

  def assert_scheme(scheme, url, context = nil)
    uri = URI.parse(url)
    assert_equal scheme, uri.scheme, message_with_context("Expected scheme to be '#{scheme}' for '#{url}'", context)
  end

  def assert_cachable_asset(page, context = nil)
    assert_status_ok page, context
    assert_max_age "315360000", page, context
    assert_public page, context
    assert_has_etag page, context
    assert_has_last_modified page, context
  end

  def assert_has_header(header, page, context = nil)
    assert page.response.key?(header), message_with_context("Expected to find '#{header}' header".freeze, context)
  end

  def assert_has_etag(page, context = nil)
    assert_includes page.response.keys, "etag", message_with_context("Expected to find an ETag header", context)
  end
  def refute_has_etag(page, context = nil)
    refute_includes page.response.keys, "etag", message_with_context("Expected not to find an ETag header", context)
  end

  def assert_has_last_modified(page, context = nil)
    assert_includes page.response.keys, "last-modified", message_with_context("Expected to find a Last-Modified header", context)
  end

  def assert_max_age(expected, page, context = nil)
    assert_equal expected, cache_max_age(page), message_with_context("Expected Cache-Control 'max-age' directive to be #{expected}", context)
  end

  def assert_must_revalidate(page, context = nil)
    assert cache_must_revalidate?(page), message_with_context("Expected Cache-Control 'must-revalidate' directive to be found", context)
  end

  def refute_must_revalidate(page, context = nil)
    refute cache_must_revalidate?(page), message_with_context("Expected Cache-Control 'must-revalidate' directive not to be found", context)
  end

  def assert_private(page, context = nil)
    assert cache_private?(page), message_with_context("Expected Cache-Control directive to be private", context)
  end

  def assert_public(page, context = nil)
    assert cache_public?(page), message_with_context("Expected Cache-Control directive to be public", context)
  end

  def assert_status_ok(page, context = nil)
    assert_code("200", page, context)
  end

  def assert_status_not_modified(page, context = nil)
    assert_code("304", page, context)
  end

  def assert_code(expected_code, page_or_code, context = nil)
    actual_code = page_or_code.respond_to?(:code) ? page_or_code.code : page_or_code
    assert_equal expected_code, actual_code, message_with_context("Expected HTTP status code to be #{expected_code}", context)
  end

  def assert_has_x_cache(page, context = nil)
    assert_includes page.response.keys, "x-cache", message_with_context("Expected to find an X-Cache header", context)
  end
  def assert_x_cache_hit(page, context = nil)
    assert_equal "HIT", x_cache_header(page), message_with_context("Expected X-Cache header to be be HIT", context)
  end
  def assert_x_cache_miss(page, context = nil)
    assert_equal "MISS", x_cache_header(page), message_with_context("Expected X-Cache header to be be MISS", context)
  end

  # Helper methods

  def header(page, key)
    page.response[key]
  end

  def status_header(page)
    header(page, "status".freeze)
  end

  def etag_header(page)
    header(page, "etag".freeze)
  end

  def last_modified_header(page)
    header(page, "last-modified".freeze)
  end

  def cache_control_header(page)
    header(page, "cache-control".freeze)
  end

  def x_cache_header(page)
    header(page, "x-cache".freeze)
  end

  def cache_private?(page)
    cache_control_directives(page).include?("private")
  end

  def cache_public?(page)
    cache_control_directives(page).include?("public")
  end

  def cache_no_cache?(page)
    cache_control_header(page).downcase.strip == "no-cache"
  end

  def cache_must_revalidate?(page)
    cache_control_directives(page).include?("must-revalidate")
  end

  def cache_has_max_age?(page)
    cache_control_directives(page).any? { |v| v[/\Amax-age=\d\Z/] }
  end

  def cache_control_directives(page)
    cache_control_header(page).downcase.split(',').map(&:strip)
  end

  def cache_max_age(page)
    pattern = /\Amax-age\s?=\s?(\d+)\Z/
    if found = cache_control_directives(page).detect("") { |v| pattern =~ v }
      found[pattern, 1]
    end
  end

end
