package cmd

import (
	"bufio"
	"context"
	"encoding/csv"
	"flag"
	"fmt"
	"net"
	"os"
	"regexp"
	"strconv"

	"code.justin.tv/admin-services/terraform/cmd/internal/iprange"
	"github.com/google/subcommands"
	"github.com/sirupsen/logrus"
)

// ValidateEgress validates egress from a VPC flow log output
type ValidateEgress struct {
	Logger logrus.FieldLogger
}

// Name implements subcommand
func (cmd ValidateEgress) Name() string {
	return "validate-egress"
}

// Synopsis implements subcommand
func (cmd ValidateEgress) Synopsis() string {
	return "validate egress traffic from vpc flow logs output and prints unknown"
}

// Usage implements subcommands
func (cmd ValidateEgress) Usage() string {
	return `cat flow-logs.txt | validate-egress
`
}

// SetFlags implements subcommand
func (cmd *ValidateEgress) SetFlags(f *flag.FlagSet) {
}

// Execute implements subcommand
func (cmd ValidateEgress) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
	if err := cmd.execute(ctx); err != nil {
		cmd.Logger.Error(err)
		return subcommands.ExitFailure
	}

	return subcommands.ExitSuccess
}

func (cmd ValidateEgress) execute(ctx context.Context) error {
	ranges, err := iprange.GetIPRanges(ctx)
	if err != nil {
		return err
	}

	w := csv.NewWriter(os.Stdout)
	if err := w.Write([]string{
		"IPAddress",
		"Description",
		"Count",
	}); err != nil {
		return err
	}

	var stats struct {
		known   int
		unknown int
	}
	if err := cmd.scanStdin(func(line *egressLog) error {
		var match iprange.IPRange

		for _, r := range ranges {
			if r.Contains(line.IP) {
				match = r
				break
			}
		}

		if match != nil {
			stats.known++
			if err := w.Write([]string{
				line.IP.String(),
				match.Description(),
				fmt.Sprintf("%d", line.Count),
			}); err != nil {
				return err
			}
		} else {
			stats.unknown++
			if err := w.Write([]string{
				line.IP.String(),
				"unknown",
				fmt.Sprintf("%d", line.Count),
			}); err != nil {
				return err
			}
		}

		w.Flush()
		return w.Error()
	}); err != nil {
		return err
	}

	w.Flush()
	if err := w.Error(); err != nil {
		return err
	}

	cmd.Logger.Infof("known: %d", stats.known)
	cmd.Logger.Infof("unknown: %d", stats.unknown)
	return nil
}

type egressLog struct {
	IP    net.IP
	Count int
}

func (cmd ValidateEgress) scanStdin(handler func(*egressLog) error) error {
	s := bufio.NewScanner(os.Stdin)
	exp, err := regexp.Compile(`^\|\s+([\d.]*)\s+\|\s+(\d+)\s+\|$`)
	if err != nil {
		return err
	}

	endExp, err := regexp.Compile("^-+$")
	if err != nil {
		return err
	}

	for i := 0; i < 3; i++ {
		if !s.Scan() {
			return s.Err()
		}
	}

	for s.Scan() {
		if endExp.Match(s.Bytes()) {
			continue
		}

		m := exp.FindStringSubmatch(string(s.Bytes()))
		if len(m) != 3 {
			return fmt.Errorf("invalid line: %s", s.Bytes())
		}

		if len(m[1]) == 0 {
			continue
		}

		ipAddress := net.ParseIP(m[1])
		if ipAddress == nil {
			return fmt.Errorf("invalid ip: %s", m[1])
		}

		count, err := strconv.Atoi(m[2])
		if err != nil {
			return err
		}

		if err := handler(&egressLog{
			IP:    ipAddress,
			Count: count,
		}); err != nil {
			return err
		}
	}
	return s.Err()
}
