// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package tools

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"math/rand"
	"strconv"
	"time"

	"github.com/google/uuid"
)

const defaultCharRange = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

type randomGenerator interface {
	GenerateFloat01()
	GenerateString(length int, chars string)
	GenerateInteger(min, max int)
	GenerateUUID() error
}

type randomGeneratorImpl struct {
	rand *rand.Rand
}

var randomGen randomGenerator = randomGeneratorImpl{
	rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}

func (r randomGeneratorImpl) GenerateFloat01() {
	fmt.Println(r.rand.Float64())
}

func (r randomGeneratorImpl) GenerateString(length int, chars string) {
	var buf bytes.Buffer

	for i := 0; i < length; i++ {
		randIndex := r.rand.Intn(len(chars))
		randChar := chars[randIndex]
		buf.WriteByte(randChar)
	}

	fmt.Println(buf.String())
}

func (r randomGeneratorImpl) GenerateInteger(min, max int) {
	fmt.Println(r.rand.Intn(max-min) + min)
}

func (r randomGeneratorImpl) GenerateUUID() error {
	uuid, err := uuid.NewRandom()
	if err != nil {
		return err
	}
	fmt.Println(uuid.String())
	return nil
}

func RandTool(args ...string) error {

	flag := flag.NewFlagSet("random", flag.ExitOnError)

	flag.Usage = func() {
		fmt.Println(MarkdownHelp("random"))
	}

	var helpFlag bool
	var intFlag int
	var strFlag int
	var uuidFlag bool

	// Define command line flags
	flag.BoolVar(&helpFlag, "h", false, "Show help message")
	flag.BoolVar(&helpFlag, "help", false, "Show help message")
	flag.IntVar(&intFlag, "int", -1, "Generate a random integer")
	flag.IntVar(&strFlag, "str", -1, "Generate a random string")
	flag.BoolVar(&uuidFlag, "u", false, "Generate a random uuid")
	flag.BoolVar(&uuidFlag, "uuid", false, "Generate a random uuid")

	// Parse command line flags
	err := flag.Parse(args)
	if err != nil {
		return err
	}

	// Print help message if -h flag is provided
	if helpFlag {
		flag.Usage()
		return nil
	}

	if uuidFlag {
		return randomGen.GenerateUUID()
	}

	if isInputFlag(*flag, "int") {
		if flag.NArg() > 1 {
			flag.Usage()
			return errors.New("invalid number of arguments, expected 1 or 2 for --int")
		}

		max := intFlag
		min := 0

		if max <= 0 {
			return fmt.Errorf("invalid max value: %v. Must be greater than 0", max)
		}

		if flag.NArg() == 1 {
			minOpt, err := strconv.Atoi(flag.Arg(0))
			if err != nil {
				return err
			}

			min = minOpt
		}

		if min >= max {
			return fmt.Errorf("invalid min value: %v. Must be less than max value: %v", min, max)
		}

		randomGen.GenerateInteger(min, max)
		return nil
	}

	if isInputFlag(*flag, "str") {
		if flag.NArg() > 1 {
			flag.Usage()
			return errors.New("invalid number of arguments, expected 1 or 2 for --str")
		}

		length := strFlag
		chars := defaultCharRange

		if length <= 0 {
			return fmt.Errorf("invalid length value: %v. Must be greater than 0", length)
		}

		if flag.NArg() == 1 {
			chars = flag.Arg(0)
		}

		randomGen.GenerateString(length, chars)
		return nil
	}

	// Get remaining args
	if flag.NArg() > 0 {
		flag.Usage()
		return errors.New("invalid number of arguments")
	}

	randomGen.GenerateFloat01()
	return nil
}

func isInputFlag(fs flag.FlagSet, flagName string) bool {
	found := false
	fs.Visit(func(f *flag.Flag) {
		if f.Name == flagName {
			found = true
		}
	})
	return found
}
