# frozen_string_literal: false
require 'irb'
require 'csv'
require 'date'
require 'optparse'
require 'optparse/date'
require 'pathname'
require 'strscan'
require 'fileutils'
require 'yaml'
require 'highline/import'

module Ldgr
  # Parses configuration options.
  #
  #
  # Examples
  #
  #   Ldgr::Parser.parse
  #   # => some file action
  #
  # Returns nothing on success.
  class Parser
    FILEBASE = Dir.home + '/.config/ledger/'
    VERSION = Ldgr::VERSION
    PROGRAM_NAME = 'ldgr'
    MATCH = /(?=(\n\d\d\d\d-\d\d-\d\d)(=\d\d\d\d-\d\d-\d\d)*)|\z/
    OTHER_MATCH = /(?=(\d\d\d\d-\d\d-\d\d)(=\d\d\d\d-\d\d-\d\d)*)/
    COMMANDS = %w(add sort tag clear open).freeze

    attr_accessor :transactions_file, :config

    # Public: Creates a new Parser object
    #
    # config  - A hash of config options
    #
    # Examples
    #
    #   new(config: {currency: '¥'})
    #   # => <ParserObject>
    #
    # Returns a Parser object.
    def initialize(config: {})
      @transactions_file = defaults.fetch(:transactions_file)
      @config = defaults.merge(user_config).merge(config)
    end

    # Public: Kicks off the CLI
    #
    # Examples
    #
    #   parse
    #
    # Returns nothing.
    def parse
      setup

      cli = OptionParser.new do |o|
        o.banner = "Usage #{PROGRAM_NAME} [add|sort|tag|clear|open]"
        o.program_name = PROGRAM_NAME
        o.version = VERSION

        o.define '-C', '--currency=CURRENCY', String, 'the currency of the transaction'
        o.define '-E', '--effective=EFFECTIVE_DATE', Date, 'the effective date of the transaction'
        o.define '-a', '--account=ACCOUNT', String, 'the account of the transaction'
        o.define '-c', '--cleared', TrueClass, 'clear the transaction'
        o.define '-d', '--date=DATE', Date, 'the date of the transaction'
        o.define '-e', '--equity=EQUITY', String, 'the equity of the transaction'
        o.define '-f', '--file=FILE', String, 'a file of transactions'
        o.define '-A', '--amount=AMOUNT', String, 'the amount of the transaction'
        o.define '-p', '--payee=PAYEE', String, 'the payee of the transaction'
      end

      command = String(cli.parse(ARGV, into: config)[0])
      send(command) if COMMANDS.include? command
    end

    # Public: Adds a transaction to the transactions_file.
    #
    # Examples
    #
    #   add
    #
    # Returns nothing.
    def add
      error_policy = ->(key) { fail "You need to provide a value for #{key.to_s}." }

      transaction = Transaction.new do |t|
        date = String(config.fetch(:date) { |key| error_policy.call(key) })
        effective = String(config.fetch(:effective) { |key| error_policy.call(key) })

        t.payee    = config.fetch(:payee) { |key| error_policy.call(key) }
        t.account  = config.fetch(:account) { |key| error_policy.call(key) }
        t.amount   = config.fetch(:amount) { |key| error_policy.call(key) }
        t.currency = config.fetch(:currency) { config.fetch(:currency) }
        t.equity   = config.fetch(:equity) { config.fetch(:equity) }
        t.cleared  = config[:cleared] ? '* ' : ''
        t.date     = date == effective ? date : date << '=' << effective
      end

      File.open(transactions_file, 'a') { |file| file.puts transaction }
    end

    # Public: Runs through all uncleared transactions that are passed
    # their effective date and offers to clear them.
    #
    # Examples
    #
    #   clear
    #
    # Returns nothing.
    def clear
      output = ''
      pattern = /((^\d{,4}-\d{,2}-\d{,2})(=\d{,4}-\d{,2}-\d{,2})?) ([^\*]+)/
      count = 0

      File.open(transactions_file, 'r') do |transactions|
        transactions.each_line do |transaction|
          match = pattern.match(transaction)
          if match && match[3]
            effective_date = Date.parse(match[3])
          else
            effective_date = Date.today
          end
          if match && Date.today >= effective_date
            count += 1
            front = match[1]
            back = match[4]
            puts transaction
            question = ask('Do you want to clear this?  ') do |q|
              q.default = 'No'
            end
            transaction.gsub!(pattern, "#{front} * #{back}") if question.match?(/y/i)
          end
          output << transaction
        end
      end
      IO.write(transactions_file, output)
    end

    # Public: Runs through all transactions with only Expenses set as the account and lets you enter an account name.
    #
    # Examples
    #
    #   tag
    #
    # Returns nothing.
    def tag
      output = ''
      pattern = /(^\s+Expenses[^:])\s*(¥.+)/
      count = 0
      previous = ''

      File.open(transactions_file, 'r') do |transactions|
        transactions.each_line do |transaction|
          match = pattern.match(transaction)
          if match
            count += 1
            puts "\n#{previous} #{match[2]}"
            question = ask('What account does this belong to?  ') { |q| q.default = 'None' }
            transaction.gsub!(match[1], "  #{question.capitalize}  ") if question != 'None'
          end
          previous = transaction.chomp
          output << transaction
        end
      end
      IO.write(transactions_file, output)
    end

    # Public: Sorts all transactions by date.
    #
    # Examples
    #
    #   sort
    #
    # Returns nothing.
    def sort
      text = File.read(transactions_file).gsub(/\n+|\r+/, "\n").squeeze("\n").strip
      scanner = StringScanner.new(text)
      results = []

      until scanner.eos?
        results << scanner.scan_until(MATCH)
        scanner.skip_until(OTHER_MATCH)
      end

      File.open(transactions_file, 'w') do |file|
        file.puts results.sort
      end
    end

    private
    # Private: User-specified config options
    #
    # Examples
    #
    #   user_config
    #   # => {all the config options from the user's YAML file}
    #
    # Returns a hash of user-specified config options.
    def user_config
      path = Pathname(FILEBASE + 'ldgr.yaml')
      path.exist? ? YAML.load_file(path).to_h : {}
    end

    # Private: Opens a settings file from ~/.config/ledger
    #
    # Examples
    #
    #   open accounts
    #   # => accounts file opens in $EDITOR
    #
    # Returns nothing.
    def open
      def open_file(file_to_open)
        checked_file = "#{FILEBASE}#{file_to_open}.dat"
        fail "#{checked_file} doesn't exist." unless Pathname(checked_file).exist?
        system(ENV['EDITOR'], checked_file)
      end

      open_file(ARGV[1])
    end

    # Private: ldgr's default configuration options
    #
    # Examples
    #
    #   defaults
    #   # => {all the configuration options}
    #
    # Returns a hash of default configuration options.
    def defaults
      {
        currency: '$',
        equity: 'Cash',
        effective: Date.today,
        date: Date.today,
        cleared: false,
        transactions_file: FILEBASE + 'transactions.dat'
      }
    end

    # Private: Prepares users' file system for ldgr.
    #
    # Returns nothing.
    def setup
      setup_files = %w(transactions.dat accounts.dat budgets.dat aliases.dat commodities.dat setup.dat ledger.dat)
      FileUtils.mkdir_p(FILEBASE)
      setup_files.each do |file|
        FileUtils.touch("#{FILEBASE}#{file}")
      end
    end
  end
end