package system

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"kra/internal/biz/system"
	"os"
	pathpkg "path"
	"path/filepath"
	"sort"
	"strings"
	"time"
)

const (
	defaultLogChunkLines = 500
	maxLogChunkBytes     = 2 * 1024 * 1024
	logReadBlockSize     = 64 * 1024
)

func (r *logFileRepo) configuredLogRoot() (root string, exists bool, err error) {
	admin := r.data.Runtime().Admin()
	if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" {
		return "", false, system.ErrLogRootUnavailable
	}
	root, err = filepath.Abs(admin.Zap.Director)
	if err != nil {
		return "", false, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	info, err := os.Stat(root)
	if errors.Is(err, fs.ErrNotExist) {
		return root, false, nil
	}
	if err != nil {
		return "", false, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	if !info.IsDir() {
		return "", false, system.ErrLogRootUnavailable
	}
	return root, true, nil
}

func (r *logFileRepo) openConfiguredLogRoot() (root *os.Root, exists bool, err error) {
	rootPath, exists, err := r.configuredLogRoot()
	if err != nil || !exists {
		return nil, exists, err
	}
	root, err = os.OpenRoot(rootPath)
	if err != nil {
		return nil, false, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	return root, true, nil
}

func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]system.LogDate, error) {
	if err := validateLogMonth(month); err != nil {
		return nil, err
	}
	logRoot, exists, err := r.openConfiguredLogRoot()
	if err != nil {
		return nil, err
	}
	if !exists {
		return []system.LogDate{}, nil
	}
	defer logRoot.Close()

	entries, err := fs.ReadDir(logRoot.FS(), ".")
	if err != nil {
		return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	result := make([]system.LogDate, 0)
	for _, entry := range entries {
		if err = ctx.Err(); err != nil {
			return nil, err
		}
		if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() || !strings.HasPrefix(entry.Name(), month+"-") {
			continue
		}
		if validateLogDate(entry.Name()) != nil {
			continue
		}
		count, countErr := countLogFiles(ctx, logRoot, entry.Name())
		if countErr != nil {
			return nil, countErr
		}
		if count > 0 {
			result = append(result, system.LogDate{Date: entry.Name(), FileCount: count})
		}
	}
	sort.Slice(result, func(i, j int) bool { return result[i].Date < result[j].Date })
	return result, nil
}

func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]system.LogFile, error) {
	if err := validateLogDate(date); err != nil {
		return nil, err
	}
	logRoot, exists, err := r.openConfiguredLogRoot()
	if err != nil {
		return nil, err
	}
	if !exists {
		return []system.LogFile{}, nil
	}
	defer logRoot.Close()

	info, err := logRoot.Lstat(date)
	if errors.Is(err, fs.ErrNotExist) {
		return []system.LogFile{}, nil
	}
	if err != nil {
		return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
		return []system.LogFile{}, nil
	}
	dateRoot, err := logRoot.OpenRoot(date)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	defer dateRoot.Close()

	result := make([]system.LogFile, 0)
	err = fs.WalkDir(dateRoot.FS(), ".", func(path string, entry fs.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if err := ctx.Err(); err != nil {
			return err
		}
		if path == "." {
			return nil
		}
		if entry.Type()&os.ModeSymlink != 0 {
			if entry.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".log") {
			return nil
		}
		fileInfo, infoErr := entry.Info()
		if infoErr != nil {
			return infoErr
		}
		if fileInfo.Mode().IsRegular() {
			result = append(result, system.LogFile{Path: path, Name: entry.Name(), Size: fileInfo.Size(), ModifiedAt: fileInfo.ModTime()})
		}
		return nil
	})
	if err != nil {
		return nil, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	sort.Slice(result, func(i, j int) bool { return result[i].Path < result[j].Path })
	return result, nil
}

func (r *logFileRepo) LogContent(ctx context.Context, date, apiPath string, cursor *int64) (*system.LogContent, error) {
	result := &system.LogContent{Date: date, Path: apiPath}
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	if cursor != nil && *cursor < 0 {
		return nil, system.ErrInvalidLogPath
	}
	file, info, err := r.openValidatedLogFile(date, apiPath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	end := info.Size()
	if cursor != nil && *cursor <= end {
		end = *cursor
	}
	start, limitedByBytes, err := findLogChunkStart(file, end)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err)
	}
	data, err := readLogRange(file, start, end)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err)
	}
	if int64(len(data)) < end-start {
		currentInfo, statErr := file.Stat()
		if statErr != nil || currentInfo.Size() < start {
			if statErr != nil {
				return nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, statErr)
			}
			return nil, system.ErrLogFileUnreadable
		}
		info = currentInfo
	}
	result.Content = string(data)
	result.LineCount = countLogicalLines(data)
	result.NextCursor = start
	result.HasMore = start > 0
	result.LimitedByBytes = limitedByBytes
	result.Size = info.Size()
	result.ModifiedAt = info.ModTime()
	return result, nil
}

func (r *logFileRepo) openValidatedLogFile(date, apiPath string) (file *os.File, info os.FileInfo, err error) {
	if err = validateLogDate(date); err != nil {
		return nil, nil, err
	}
	segments, err := validateLogAPIPath(apiPath)
	if err != nil {
		return nil, nil, err
	}
	logRoot, exists, err := r.openConfiguredLogRoot()
	if err != nil {
		return nil, nil, err
	}
	if !exists {
		return nil, nil, system.ErrLogFileNotFound
	}
	defer logRoot.Close()

	dateInfo, err := logRoot.Lstat(date)
	if errors.Is(err, fs.ErrNotExist) {
		return nil, nil, system.ErrLogFileNotFound
	}
	if err != nil {
		return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err)
	}
	if dateInfo.Mode()&os.ModeSymlink != 0 || !dateInfo.IsDir() {
		return nil, nil, system.ErrInvalidLogPath
	}
	dateRoot, err := logRoot.OpenRoot(date)
	if err != nil {
		return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err)
	}
	defer dateRoot.Close()

	relativePath := ""
	var validatedInfo os.FileInfo
	for index, segment := range segments {
		relativePath = filepath.Join(relativePath, segment)
		validatedInfo, err = dateRoot.Lstat(relativePath)
		if errors.Is(err, fs.ErrNotExist) {
			return nil, nil, system.ErrLogFileNotFound
		}
		if err != nil {
			return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err)
		}
		if validatedInfo.Mode()&os.ModeSymlink != 0 {
			return nil, nil, system.ErrInvalidLogPath
		}
		if index < len(segments)-1 && !validatedInfo.IsDir() {
			return nil, nil, system.ErrInvalidLogPath
		}
	}
	if validatedInfo == nil || !validatedInfo.Mode().IsRegular() {
		return nil, nil, system.ErrInvalidLogPath
	}
	file, err = dateRoot.Open(relativePath)
	if err != nil {
		if errors.Is(err, fs.ErrNotExist) {
			return nil, nil, system.ErrLogFileNotFound
		}
		return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, err)
	}
	openedInfo, statErr := file.Stat()
	if statErr != nil || !openedInfo.Mode().IsRegular() || !os.SameFile(validatedInfo, openedInfo) {
		file.Close()
		if statErr != nil {
			return nil, nil, fmt.Errorf("%w: %v", system.ErrLogFileUnreadable, statErr)
		}
		return nil, nil, system.ErrInvalidLogPath
	}
	return file, openedInfo, nil
}

func validateLogMonth(month string) error {
	parsed, err := time.Parse("2006-01", month)
	if err != nil || parsed.Format("2006-01") != month {
		return system.ErrInvalidLogMonth
	}
	return nil
}

func validateLogDate(date string) error {
	parsed, err := time.Parse("2006-01-02", date)
	if err != nil || parsed.Format("2006-01-02") != date {
		return system.ErrInvalidLogDate
	}
	return nil
}

func validateLogAPIPath(apiPath string) ([]string, error) {
	if apiPath == "" || strings.Contains(apiPath, "\\") || strings.Contains(apiPath, ":") || pathpkg.IsAbs(apiPath) {
		return nil, system.ErrInvalidLogPath
	}
	segments := strings.Split(apiPath, "/")
	for _, segment := range segments {
		if segment == "" || segment == "." || segment == ".." {
			return nil, system.ErrInvalidLogPath
		}
	}
	if !strings.EqualFold(pathpkg.Ext(apiPath), ".log") {
		return nil, system.ErrInvalidLogPath
	}
	return segments, nil
}

func countLogFiles(ctx context.Context, logRoot *os.Root, date string) (count int, err error) {
	dateInfo, err := logRoot.Lstat(date)
	if err != nil || dateInfo.Mode()&os.ModeSymlink != 0 || !dateInfo.IsDir() {
		return 0, err
	}
	dateRoot, err := logRoot.OpenRoot(date)
	if err != nil {
		return 0, err
	}
	defer dateRoot.Close()
	err = fs.WalkDir(dateRoot.FS(), ".", func(path string, entry fs.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if err := ctx.Err(); err != nil {
			return err
		}
		if path == "." {
			return nil
		}
		if entry.Type()&os.ModeSymlink != 0 {
			if entry.IsDir() {
				return filepath.SkipDir
			}
			return nil
		}
		if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".log") {
			return nil
		}
		info, infoErr := entry.Info()
		if infoErr != nil {
			return infoErr
		}
		if info.Mode().IsRegular() {
			count++
		}
		return nil
	})
	if err != nil {
		return 0, fmt.Errorf("%w: %v", system.ErrLogRootUnavailable, err)
	}
	return count, nil
}

func readLogRange(file *os.File, start, end int64) ([]byte, error) {
	if end <= start {
		return []byte{}, nil
	}
	data := make([]byte, int(end-start))
	n, err := file.ReadAt(data, start)
	if err != nil && !errors.Is(err, io.EOF) {
		return nil, err
	}
	return data[:n], nil
}

func findLogChunkStart(file *os.File, end int64) (start int64, limitedByBytes bool, err error) {
	if end <= 0 {
		return 0, false, nil
	}
	lastByte := []byte{0}
	if _, err = file.ReadAt(lastByte, end-1); err != nil {
		return 0, false, err
	}
	targetNewlines := defaultLogChunkLines
	if lastByte[0] == '\n' {
		targetNewlines++
	}
	position, scanned, newlines := end, int64(0), 0
	for position > 0 && scanned < maxLogChunkBytes {
		readSize := int64(logReadBlockSize)
		if readSize > position {
			readSize = position
		}
		if remaining := int64(maxLogChunkBytes) - scanned; readSize > remaining {
			readSize = remaining
		}
		blockStart := position - readSize
		block := make([]byte, int(readSize))
		n, readErr := file.ReadAt(block, blockStart)
		if readErr != nil && !errors.Is(readErr, io.EOF) {
			return 0, false, readErr
		}
		for index := n - 1; index >= 0; index-- {
			if block[index] == '\n' {
				newlines++
				if newlines == targetNewlines {
					return blockStart + int64(index) + 1, false, nil
				}
			}
		}
		scanned += int64(n)
		position = blockStart
	}
	if position > 0 {
		return end - int64(maxLogChunkBytes), true, nil
	}
	return 0, false, nil
}

func countLogicalLines(data []byte) int {
	if len(data) == 0 {
		return 0
	}
	count := bytes.Count(data, []byte{'\n'})
	if data[len(data)-1] != '\n' {
		count++
	}
	return count
}
