package lh

import (
	"bytes"
	"io"
	"strings"
	"sync"
	"time"

	"github.com/olekukonko/ll/lx"
)

type TextOption func(*TextHandler)

var textBufPool = sync.Pool{
	New: func() any {
		return new(bytes.Buffer)
	},
}

// WithTextTimeFormat enables timestamp display and optionally sets a custom time format.
// It configures the TextHandler to include temporal information in each log entry,
// allowing for precise tracking of when log events occur.
// If the format string is empty, it defaults to time.RFC3339.
func WithTextTimeFormat(format string) TextOption {
	return func(t *TextHandler) {
		t.Timestamped(true, format)
	}
}

// WithTextShowTime enables or disables timestamp display in log entries.
// This option provides direct control over the visibility of the time prefix
// without altering the underlying time format configured in the handler.
// Setting show to true will prepend timestamps to all subsequent regular log outputs.
func WithTextShowTime(show bool) TextOption {
	return func(t *TextHandler) {
		t.showTime = show
	}
}

// TextHandler is a handler that outputs log entries as plain text.
// It formats log entries with namespace, level, message, fields, and optional stack traces,
// writing the result to the provided writer.
// Thread-safe if the underlying writer is thread-safe.
type TextHandler struct {
	writer     io.Writer // Destination for formatted log output
	showTime   bool      // Whether to display timestamps
	timeFormat string    // Format for timestamps (defaults to time.RFC3339)
	mu         sync.Mutex
}

// NewTextHandler creates a new TextHandler writing to the specified writer.
// It initializes the handler with the given writer, suitable for outputs like stdout or files.
// Example:
//
//	handler := NewTextHandler(os.Stdout)
//	logger := ll.New("app").Enable().Handler(handler)
//	logger.Info("Test") // Output: [app] INFO: Test
func NewTextHandler(w io.Writer, opts ...TextOption) *TextHandler {
	t := &TextHandler{
		writer:     w,
		showTime:   false,
		timeFormat: time.RFC3339,
	}

	for _, opt := range opts {
		opt(t)
	}

	return t
}

// Timestamped enables or disables timestamp display and optionally sets a custom time format.
// If format is empty, defaults to RFC3339.
// Example:
//
//	handler := NewTextHandler(os.Stdout).TextWithTime(true, time.StampMilli)
//	// Output: Jan 02 15:04:05.000 [app] INFO: Test
func (h *TextHandler) Timestamped(enable bool, format ...string) {
	h.showTime = enable
	if len(format) > 0 && format[0] != "" {
		h.timeFormat = format[0]
	}
}

// Output sets a new writer for the TextHandler.
// Thread-safe - safe for concurrent use.
func (h *TextHandler) Output(w io.Writer) {
	h.mu.Lock()
	defer h.mu.Unlock()
	h.writer = w
}

// Handle processes a log entry and writes it as plain text.
// It delegates to specialized methods based on the entry's class (Dump, Raw, or regular).
// Returns an error if writing to the underlying writer fails.
// Thread-safe if the writer is thread-safe.
// Example:
//
//	handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes "INFO: test"
func (h *TextHandler) Handle(e *lx.Entry) error {
	h.mu.Lock()
	defer h.mu.Unlock()

	if e.Class == lx.ClassDump {
		return h.handleDumpOutput(e)
	}

	if e.Class == lx.ClassRaw {
		_, err := h.writer.Write([]byte(e.Message))
		return err
	}

	return h.handleRegularOutput(e)
}

// handleRegularOutput handles normal log entries.
// It formats the entry with namespace, level, message, fields, and stack trace (if present),
// writing the result to the handler's writer.
// Returns an error if writing fails.
// Example (internal usage):
//
//	h.handleRegularOutput(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes "INFO: test"
func (h *TextHandler) handleRegularOutput(e *lx.Entry) error {
	buf := textBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	defer textBufPool.Put(buf)

	if h.showTime {
		buf.WriteString(e.Timestamp.Format(h.timeFormat))
		buf.WriteString(lx.Space)
	}

	switch e.Style {
	case lx.NestedPath:
		if e.Namespace != "" {
			parts := strings.Split(e.Namespace, lx.Slash)
			for i, part := range parts {
				buf.WriteString(lx.LeftBracket)
				buf.WriteString(part)
				buf.WriteString(lx.RightBracket)
				if i < len(parts)-1 {
					buf.WriteString(lx.Arrow)
				}
			}
			buf.WriteString(lx.Colon)
			buf.WriteString(lx.Space)
		}
	default: // FlatPath
		if e.Namespace != "" {
			buf.WriteString(lx.LeftBracket)
			buf.WriteString(e.Namespace)
			buf.WriteString(lx.RightBracket)
			buf.WriteString(lx.Space)
		}
	}

	buf.WriteString(e.Level.Name(e.Class))
	// buf.WriteString(lx.Space)
	buf.WriteString(lx.Colon)
	buf.WriteString(lx.Space)
	buf.WriteString(e.Message)

	if len(e.Fields) > 0 {
		buf.WriteString(lx.Space)
		buf.WriteString(lx.LeftBracket)
		for i, pair := range e.Fields {
			if i > 0 {
				buf.WriteString(lx.Space)
			}
			buf.WriteString(pair.Key)
			buf.WriteString("=")
			writeFieldValue(buf, pair.Value)
		}
		buf.WriteString(lx.RightBracket)
	}

	if len(e.Stack) > 0 {
		h.formatStack(buf, e.Stack)
	}

	if e.Level != lx.LevelNone {
		buf.WriteString(lx.Newline)
	}

	_, err := h.writer.Write(buf.Bytes())
	return err
}

// handleDumpOutput specially formats hex dump output (plain text version).
// It wraps the dump message with BEGIN/END separators for clarity.
// Returns an error if writing fails.
// Example (internal usage):
//
//	h.handleDumpOutput(&lx.Entry{Class: lx.ClassDump, Message: "pos 00 hex: 61"}) // Writes "---- BEGIN DUMP ----\npos 00 hex: 61\n---- END DUMP ----\n"
func (h *TextHandler) handleDumpOutput(e *lx.Entry) error {
	buf := textBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	defer textBufPool.Put(buf)

	if h.showTime {
		buf.WriteString(e.Timestamp.Format(h.timeFormat))
		buf.WriteString(lx.Newline)
	}

	buf.WriteString("---- BEGIN DUMP ----\n")
	buf.WriteString(e.Message)
	buf.WriteString("---- END DUMP ----\n\n")

	_, err := h.writer.Write(buf.Bytes())
	return err
}

// formatStack formats a stack trace for plain text output.
// It structures the stack trace with indentation and separators for readability,
// including goroutine and function/file details.
// Example (internal usage):
//
//	h.formatStack(&builder, []byte("goroutine 1 [running]:\nmain.main()\n\tmain.go:10")) // Appends formatted stack trace
func (h *TextHandler) formatStack(b *bytes.Buffer, stack []byte) {
	lines := strings.Split(string(stack), "\n")
	if len(lines) == 0 {
		return
	}

	b.WriteString("\n[stack]\n")

	b.WriteString("  ┌─ ")
	b.WriteString(lines[0])
	b.WriteString("\n")

	for i := 1; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		if line == "" {
			continue
		}

		if strings.Contains(line, ".go") {
			b.WriteString("  ├       ")
		} else {
			b.WriteString("  │   ")
		}
		b.WriteString(line)
		b.WriteString("\n")
	}

	b.WriteString("  └\n")
}
