package cmd

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"

	"github.com/itchyny/gojq"
	"github.com/mattn/go-isatty"
)

func writeFormattedJSON(w io.Writer, raw []byte) error {
	formatted, err := formatJSONForWriter(raw, shouldColorizeWriter(w))
	if err != nil {
		return err
	}
	_, err = w.Write(formatted)
	return err
}

func writeFormattedJSONOrRaw(w io.Writer, raw []byte) error {
	formatted, err := formatJSONForWriter(raw, shouldColorizeWriter(w))
	if err != nil {
		_, err = w.Write(raw)
		return err
	}
	_, err = w.Write(formatted)
	return err
}

const (
	ansiReset     = "\x1b[0m"
	ansiNull      = "\x1b[90m"
	ansiBool      = "\x1b[33m"
	ansiNumber    = "\x1b[36m"
	ansiString    = "\x1b[32m"
	ansiObjectKey = "\x1b[34;1m"
)

func formatJSONForWriter(raw []byte, colorize bool) ([]byte, error) {
	decoded, err := decodeJSON(raw)
	if err != nil {
		return nil, err
	}

	if colorize {
		return formatJSONColor(decoded)
	}
	return formatJSON(decoded)
}

func decodeJSON(raw []byte) (any, error) {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 {
		return nil, fmt.Errorf("empty json input")
	}

	var decoded any
	decoder := json.NewDecoder(bytes.NewReader(trimmed))
	if err := decoder.Decode(&decoded); err != nil {
		return nil, fmt.Errorf("decode json: %w", err)
	}
	return decoded, nil
}

func formatJSON(decoded any) ([]byte, error) {
	jqEncoded, err := gojq.Marshal(decoded)
	if err != nil {
		return nil, fmt.Errorf("gojq marshal: %w", err)
	}

	var output bytes.Buffer
	if err := json.Indent(&output, jqEncoded, "", "  "); err != nil {
		return nil, fmt.Errorf("indent json: %w", err)
	}
	output.WriteByte('\n')
	return output.Bytes(), nil
}

func formatJSONColor(decoded any) ([]byte, error) {
	var output bytes.Buffer
	encoder := newGojqColorEncoder(2)
	if err := encoder.marshal(decoded, &output); err != nil {
		return nil, err
	}
	output.WriteByte('\n')
	return output.Bytes(), nil
}

type writerFileDescriptor interface {
	Fd() uintptr
}

func shouldColorizeWriter(w io.Writer) bool {
	if os.Getenv("NO_COLOR") != "" || os.Getenv("TERM") == "dumb" {
		return false
	}

	fileDescriptor, ok := w.(writerFileDescriptor)
	if !ok {
		return false
	}

	fd := fileDescriptor.Fd()
	return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
}
