#!/usr/bin/env ruby

require 'fileutils'
require 'pathname'
require 'tempfile'
require 'securerandom'
require 'minitar'
require 'zlib'
require 'find'
require 'net/http'
require 'net/http/post/multipart'
require 'uri'
require 'listen'
require 'json'

# Work in progress theme watcher for Discourse
#
# Monitor a theme directory locally and automatically keep it in sync with Discourse

def usage
  puts "Usage: theme-watcher DIR SITE"
  exit 1
end

WATCHER_SETTINGS_FILE = File.expand_path("~/.discourse-theme-watcher")

$api_key = ENV['DISCOURSE_API_KEY']
$dir = ARGV[0]
$site = ARGV[1]
$theme_id = nil

if $site !~ /https?:\/\//i
  $site = "http://#{$site}"
end

puts "Watching #{$dir} and uploading changes to #{$site}"

if !$api_key && File.exist?(WATCHER_SETTINGS_FILE)
  $api_key = File.read(WATCHER_SETTINGS_FILE).strip
  puts "Using previously stored api key in #{WATCHER_SETTINGS_FILE}"
end

if !$api_key
  puts "No API key found in DISCOURSE_API_KEY env var enter your API key: "
  $api_key = STDIN.gets.strip
  puts "Would you like me to store this API key in #{WATCHER_SETTINGS_FILE}? (Yes|No)"
  answer = STDIN.gets.strip
  if answer =~ /y(es)?/i
    File.write WATCHER_SETTINGS_FILE, $api_key
  end
end

if !File.exist?("#{$dir}/about.json")
  puts "No about.json file found in #{dir}!"
  puts
  usage
end

def compress_dir(gzip, dir)
  sgz = Zlib::GzipWriter.new(File.open(gzip, 'wb'))
  tar = Archive::Tar::Minitar::Output.new(sgz)

  Dir.chdir(dir + "/../") do
    Find.find(File.basename(dir)) do |x|
      Find.prune if File.basename(x)[0] == ?.
      next if File.directory?(x)

      Minitar.pack_file(x, tar)
    end
  end
ensure
  tar.close
  sgz.close
end

def diagnose_errors(json)
  count = 0
  json["theme"]["theme_fields"].each do |row|
    if (error = row["error"]) && error.length > 0
      if count == 0
        puts
      end
      count += 1
      puts
      puts "Error in #{row["target"]} #{row["name"]}: #{row["error"]}"
      puts
    end
  end
  count
end

def upload_theme_field(target: , name: , type_id: , value:)
  args = {
    theme: {
      theme_fields: [{
        name: name,
        target: target,
        type_id: type_id,
        value: value
      }]
    }
  }

  uri = URI.parse($site + "/admin/themes/#{$theme_id}?api_key=#{$api_key}")

  http = Net::HTTP.new(uri.host, uri.port)
  request = Net::HTTP::Put.new(uri.request_uri, 'Content-Type' => 'application/json')
  request.body = args.to_json

  http.start do |h|
    response = h.request(request)
    if response.code.to_i == 200
      json = JSON.parse(response.body)
      if diagnose_errors(json) == 0
        puts "(done)"
      end
    else
      puts "Error importing field status: #{response.code}"
    end
  end
end

def upload_full_theme(dir, site)
  filename = "#{Pathname.new(Dir.tmpdir).realpath}/bundle_#{SecureRandom.hex}.tar.gz"
  compress_dir(filename, dir)

  # new full upload endpoint
  uri = URI.parse(site + "/admin/themes/import.json?api_key=#{$api_key}")
  http = Net::HTTP.new(uri.host, uri.port)
  File.open(filename) do |tgz|

    request = Net::HTTP::Post::Multipart.new(
      uri.request_uri,
      "bundle" => UploadIO.new(tgz, "application/tar+gzip", "bundle.tar.gz"),
    )
    response = http.request(request)
    if response.code.to_i == 201
      json = JSON.parse(response.body)
      $theme_id = json["theme"]["id"]
      if diagnose_errors(json) == 0
        puts "(done)"
      end
    else
      puts "Error importing theme status: #{response.code}"
    end
  end

ensure
  FileUtils.rm_f filename
end

print "Uploading theme: "
upload_full_theme($dir, $site)

def resolve_file(path)
  dir_len = File.expand_path($dir).length
  name = File.expand_path(path)[dir_len + 1..-1]

  target, file = name.split("/")

  if ["common", "desktop", "mobile"].include?(target)
    if file = "#{target}.scss"
      # a CSS file
      return [target, "scss", 1]
    end
  end

  nil
end

listener = Listen.to($dir) do |modified, added, removed|
  if modified.length == 1 &&
      added.length == 0 &&
      removed.length == 0 &&
      (target, name, type_id = resolve_file(modified[0]))
    print "Updating #{target} #{name}: "
    upload_theme_field(target: target, name: name, value: File.read(modified[0]), type_id: type_id)
  else
    print "Full re-sync is required, re-uploading theme: "
    upload_full_theme($dir, $site)
  end
end

listener.start
sleep