# frozen_string_literal: true

require 'fileutils'
require 'json'
require 'minitar'
require 'sitediff'
require 'sitediff/config'
require 'zlib'

class SiteDiff
  ##
  # SiteDiff Report Helper.
  class Report
    attr_reader :results, :cache

    ##
    # Directory where diffs will be generated.
    DIFFS_DIR = 'diffs'

    ##
    # Name of file containing a list of pages with diffs.
    FAILURES_FILE = 'failures.txt'

    ##
    # Name of file containing HTML report of diffs.
    REPORT_FILE_HTML = 'report.html'

    ##
    # Name of file containing JSON report of diffs.
    REPORT_FILE_JSON = 'report.json'

    ##
    # Name of file containing exported file archive.
    REPORT_FILE_TAR = 'report.tgz'

    ##
    # Name of directory in which to build the portable report.
    REPORT_BUILD_DIR = '_tmp_report'

    ##
    # Name of the portable report directory.
    REPORT_DIR = 'report'

    ##
    # Path to settings used for report.
    SETTINGS_FILE = 'settings.yaml'

    ##
    # Creates a Reporter object.
    #
    # @param [Config] config.
    # @param [Cache] cache.
    # @param [Array] results.
    def initialize(config, cache, results)
      @config = config
      @cache = cache
      @results = results
    end

    ##
    # Generates an HTML report.
    #
    # @param [String] dir
    #   The directory in which the report is to be generated.
    def generate_html(
      dir,
      report_before = nil,
      report_after = nil
    )
      report_before ||= @config.before_url
      report_after ||= @config.after_url
      @config.before_time = get_timestamp(:before)
      @config.after_time = get_timestamp(:after)

      dir = SiteDiff.ensure_dir dir

      write_diffs dir
      write_failures dir

      # Prepare report.
      report = Diff.generate_html(
        @results,
        report_before,
        report_after,
        @cache,
        @config
      )

      # Write report.
      report_file = dir + REPORT_FILE_HTML
      report_file.unlink if report_file.file?
      report_file.open('w') { |f| f.write(report) }

      write_settings dir, report_before, report_after

      if @config.export
        package_report(dir)
      else
        SiteDiff.log "Report generated to #{report_file.expand_path}"
      end
    end

    ##
    # Generates a JSON report.
    #
    # @param dir
    #   The directory in which the report is to be generated.
    def generate_json(dir)
      dir = SiteDiff.ensure_dir dir
      write_diffs dir
      write_failures dir

      # Prepare report.
      report = {
        paths_compared: @results.length,
        paths_diffs: 0,
        paths: {}
      }
      @results.each do |item|
        report[:paths_diffs] += 1 unless item.success?

        item_report = {
          path: item.path,
          status: item.status,
          message: item.error
        }
        report[:paths][item.path] = item_report
      end
      report = JSON report

      # Write report.
      report_file = dir + REPORT_FILE_JSON
      report_file.unlink if report_file.file?
      report_file.open('w') { |f| f.write(report) }

      write_settings dir

      SiteDiff.log "Report generated to #{report_file.expand_path}"
    end

    ##
    # Package report for export.
    def package_report(dir)
      # Create temporaryreport directories.
      temp_path = dir + REPORT_BUILD_DIR
      temp_path.rmtree if temp_path.directory?
      temp_path.mkpath
      report_path = temp_path + REPORT_DIR
      report_path.mkpath
      files_path = "#{report_path}/files"
      FileUtils.mkpath(files_path)
      diffs_path = dir + DIFFS_DIR

      # Move files to place.
      FileUtils.move(dir + REPORT_FILE_HTML, report_path)
      FileUtils.move(diffs_path, files_path) if diffs_path.directory?

      # Make tar file.
      Dir.chdir(temp_path) do
        Minitar.pack(
          REPORT_DIR,
          Zlib::GzipWriter.new(File.open(REPORT_FILE_TAR, 'wb'))
        )
      end
      FileUtils.move(temp_path + REPORT_FILE_TAR, dir)
      temp_path.rmtree
      SiteDiff.log "Archived report generated to #{dir.join(REPORT_FILE_TAR)}"
    end

    ##
    # Creates diff files in a directory named "diffs".
    #
    # If "dir" is /foo/bar, then diffs will be placed in /foo/bar/diffs.
    #
    # @param [Pathname] dir
    #   The directory in which a "diffs" directory is to be generated.
    def write_diffs(dir)
      raise Exception 'dir must be a Pathname' unless dir.is_a? Pathname

      # Delete existing "diffs" dir, if exists.
      diff_dir = dir + DIFFS_DIR
      diff_dir.rmtree if diff_dir.exist?

      # Write diffs to the diff directory.
      @results.each { |r| r.dump(dir, relative: @config.export) if r.status == Result::STATUS_FAILURE }
      SiteDiff.log "All diff files written to #{diff_dir.expand_path}" unless @config.export
    end

    ##
    # Writes paths with diffs into a file.
    #
    # @param [Pathname] dir
    #   The directory in which the report is to be generated.
    def write_failures(dir)
      raise Exception 'dir must be a Pathname' unless dir.is_a? Pathname

      failures = dir + FAILURES_FILE
      SiteDiff.log "All failures written to #{failures.expand_path}"
      failures.open('w') do |f|
        @results.each { |r| f.puts r.path unless r.success? }
      end
    end

    ##
    # Creates report settings.yaml file.
    #
    # TODO: Find a way to avoid having to create this file.
    #
    # @param [Pathname] dir
    #   The directory in which the report is to be generated.
    def write_settings(dir, report_before = nil, report_after = nil)
      raise Exception 'dir must be a Pathname' unless dir.is_a? Pathname

      settings = {
        'before' => report_before,
        'after' => report_after,
        'cached' => %w[before after]
      }
      dir.+(SETTINGS_FILE).open('w') { |f| YAML.dump(settings, f) }
    end

    ##
    # Returns CSS for HTML report.
    def self.css
      output = ''
      output += File.read(File.join(SiteDiff::FILES_DIR, 'normalize.css'))
      output += File.read(File.join(SiteDiff::FILES_DIR, 'sitediff.css'))
      output
    end

    ##
    # Returns JS for HTML report.
    def self.js
      output = ''
      output += File.read(File.join(SiteDiff::FILES_DIR, 'jquery.min.js'))
      output += File.read(File.join(SiteDiff::FILES_DIR, 'sitediff.js'))
      output
    end

    private

    # Get crawl timestamps
    def get_timestamp(tag)
      timestamp_file = File.join(@config.directory, 'snapshot', tag.to_s, SiteDiff::Cache::TIMESTAMP_FILE)
      if File.exist? timestamp_file
        file = File::Stat.new(timestamp_file)
        time = file.mtime
        time.instance_of?(Time) ? time.strftime('%Y-%m-%d %H:%M') : ''
      else
        'unknown'
      end
    end
  end
end
