package server

import (
	"fmt"
	"os"
	"unicode/utf8"
)

// OutputWindow represents a UTF-8-safe slice of output.log.
type OutputWindow struct {
	Output    string `json:"output"`
	Offset    int    `json:"offset"`
	Truncated bool   `json:"truncated"`
}

// ReadOutputWindow reads a forward UTF-8-safe slice from an output file.
func ReadOutputWindow(path string, offset, limit int) (*OutputWindow, error) {
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		if offset == 0 {
			return &OutputWindow{Output: "", Offset: 0, Truncated: false}, nil
		}
		return nil, NewServerError(400, "invalid_offset", "offset exceeds current file size 0")
	}
	if err != nil {
		return nil, err
	}

	size := int(info.Size())
	if offset > size {
		return nil, NewServerError(400, "invalid_offset",
			fmt.Sprintf("offset %d exceeds current file size %d", offset, size))
	}
	if offset == size {
		return &OutputWindow{Output: "", Offset: offset, Truncated: false}, nil
	}

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer func() { _ = f.Close() }()

	// Read up to limit+4 bytes to handle UTF-8 boundary
	readSize := limit + 4
	if offset+readSize > size {
		readSize = size - offset
	}

	buf := make([]byte, readSize)
	_, err = f.ReadAt(buf, int64(offset))
	if err != nil {
		return nil, err
	}

	// Advance past any UTF-8 continuation bytes at the start
	startShift := advanceToUTF8Boundary(buf, 0)
	data := buf[startShift:]
	budget := limit - startShift
	if budget < 0 {
		budget = 0
	}

	// Find the longest valid UTF-8 prefix within budget
	consumed := validUTF8PrefixLen(data, budget)
	if consumed == 0 && len(data) > 0 {
		// At least consume one complete rune
		consumed = firstCompleteRuneLen(data)
	}

	outputBytes := data[:consumed]
	newOffset := offset + startShift + consumed
	truncated := newOffset < size

	return &OutputWindow{
		Output:    string(outputBytes),
		Offset:    newOffset,
		Truncated: truncated,
	}, nil
}

// TailOutputWindow reads a UTF-8-safe tail snapshot from an output file.
func TailOutputWindow(path string, limit int) (*OutputWindow, error) {
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		return &OutputWindow{Output: "", Offset: 0, Truncated: false}, nil
	}
	if err != nil {
		return nil, err
	}

	size := int(info.Size())
	if size == 0 {
		return &OutputWindow{Output: "", Offset: 0, Truncated: false}, nil
	}

	start := size - limit
	if start < 0 {
		start = 0
	}
	paddedStart := start - 4
	if paddedStart < 0 {
		paddedStart = 0
	}

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer func() { _ = f.Close() }()

	readLen := size - paddedStart
	buf := make([]byte, readLen)
	_, err = f.ReadAt(buf, int64(paddedStart))
	if err != nil {
		return nil, err
	}

	relativeStart := advanceToUTF8Boundary(buf, start-paddedStart)
	payload := buf[relativeStart:]
	consumed := validUTF8PrefixLen(payload, len(payload))
	outputBytes := payload[:consumed]

	return &OutputWindow{
		Output:    string(outputBytes),
		Offset:    paddedStart + relativeStart + consumed,
		Truncated: false,
	}, nil
}

// advanceToUTF8Boundary skips continuation bytes (10xxxxxx) at position start.
func advanceToUTF8Boundary(data []byte, start int) int {
	for start < len(data) && isUTF8Continuation(data[start]) {
		start++
	}
	return start
}

// isUTF8Continuation returns true if byte is a UTF-8 continuation byte.
func isUTF8Continuation(b byte) bool {
	return (b & 0xC0) == 0x80
}

// validUTF8PrefixLen returns the length of the longest valid UTF-8 prefix up to maxLen.
func validUTF8PrefixLen(data []byte, maxLen int) int {
	if maxLen > len(data) {
		maxLen = len(data)
	}
	end := maxLen
	for end > 0 {
		if utf8.Valid(data[:end]) {
			return end
		}
		end--
	}
	return 0
}

// firstCompleteRuneLen returns the byte length of the first complete rune in data.
func firstCompleteRuneLen(data []byte) int {
	for end := 1; end <= len(data) && end <= 4; end++ {
		if utf8.Valid(data[:end]) {
			return end
		}
	}
	return 0
}
