#!/usr/bin/env ruby
# frozen_string_literal: true

# Ruby internal
require 'pp'
# Project internal
require 'tls_map'
require 'tls_map/cli'
# External
require 'docopt'
require 'paint'

# can't specify 2 options with the same name even if used in different commands
# https://github.com/docopt/docopt/issues/296#issuecomment-857477191
doc = <<~DOCOPT
  TLS map #{TLSmap::VERSION}

  Usage:
    tls-map search <critera> <term> [-o <output> --force -e -a] [--no-color --debug]
    tls-map bulk <critera> <file> [-q <output> --force] [--no-color --debug]
    tls-map export <filename> <format> [--force] [--debug]
    tls-map extract <filename> <format> [--no-color --debug]
    tls-map update [--debug]
    tls-map -h | --help
    tls-map --version

  Search options: (offline) search and translate cipher names between SSL/TLS libraries
    <critera>               The type of term. Accepted values: codepoint, iana, openssl, gnutls, nss.
    <term>                  The cipher algorithm name.
    -o, --output <output>   Displayed fields. Accepted values: all, codepoint, iana, openssl, gnutls, nss. [default: all]
    -e, --extended          (Online) Display additional information about the cipher (requires output = all or iana)
    -a, --acronym           (Online) Display full acronym name (requires -e / --extended option)

  Bulk options: (offline) search and translate cipher names between SSL/TLS libraries in bulk
    <critera>               The type of term. Accepted values: codepoint, iana, openssl, gnutls, nss.
    <file>                  File containing the cipher algorithm names, one per line.
    -q, --output2 <output>  Displayed fields. Accepted values: codepoint, iana, openssl, gnutls, nss. [default: iana]

  Export options: (offline) export the list of all ciphers (mapping) in various formats
    <filename>              The output file name to write to.
    <format>                Supported formats: markdown (a markdown table), json_pretty (expanded JSON), json_compact (minified JSON), marshal (Ruby marshalized hash).

  Extract options: (offline) extract ciphers from external tools output file
    <filename>              The external tool output file
    <format>                Supported formats: sslyze, sslscan2, testssl, ssllabs-scan (check the documentation for the expected file format)

  Update options: (online) DANGEROUS, will break database integrity, force option will be required

  Other options:
    --force     Force parsing even if integrity check failed (DANGEROUS, may result in command execution vulnerability)
    --no-color  Disable colorized output
    --debug     Display arguments
    -h, --help  Show this screen
    --version   Show version
DOCOPT

begin
  args = Docopt.docopt(doc, version: TLSmap::VERSION)
  Paint.mode = 0 if args['--no-color']
  pp args if args['--debug']
  if args['search']
    cli = TLSmap::CLI.new(args['--force'])
    res = cli.search(args['<critera>'].to_sym, args['<term>'], args['--output'].to_sym)
    puts Paint['No match found', :red] if res.empty?
    res.each do |k, v|
      puts "#{Paint[k, :green]}: #{Paint[v, :white]}"
    end
    if args['--extended']
      tmext = TLSmap::App::Extended
      tmext_i = tmext.new
      ext = tmext_i.extend(res[:iana])
      dic = tmext::DICO
      sev = tmext::VULN_SEVERITY
      ext.each do |k, v|
        case k
        when 'vulns'
          puts "#{Paint[dic[k], :magenta]}:"
          v.each do |vuln|
            print "  - #{Paint[sev[vuln[:severity]][:title], sev[vuln[:severity]][:color]]} - "
            puts Paint[vuln[:description], :white]
          end
        when 'tls_version'
          puts "#{Paint[dic[k], :magenta]}: #{Paint[v.join(', '), :white]}"
        else
          print "#{Paint[dic[k], :magenta]}: #{Paint[v, :white]}"
          print " (#{tmext_i.translate_acronym(v)})" if args['--acronym'] && !tmext_i.translate_acronym(v).nil? # rubocop:disable Metrics/BlockNesting
          puts
        end
      end
    end
  elsif args['bulk']
    cli = TLSmap::CLI.new(args['--force'])
    res = cli.bulk_search(args['<critera>'].to_sym, args['<file>'], args['--output2'].to_sym)
    puts Paint['No match found', :red] if res.empty?
    res.each do |h|
      puts Paint[h[args['--output2'].to_sym], :green]
    end
  elsif args['export']
    cli = TLSmap::CLI.new(args['--force'])
    cli.export(args['<filename>'], args['<format>'].to_sym)
    puts "#{args['<filename>']} exported"
  elsif args['extract']
    extractor = TLSmap::App::Extractor.new
    ciphers = extractor.parse(args['<format>'], args['<filename>'])
    ciphers.each do |k, v|
      puts Paint[k, :blue] unless v.empty?
      puts Paint[v.join("\n"), :white] unless v.empty?
    end
  elsif args['update']
    cli = TLSmap::CLI.new
    cli.update
    puts 'Database updated'
  end
rescue Docopt::Exit => e
  puts e.message
end
