package com.easyagents.agent.runtime.tool.operate;

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.Set;

/**
 * 工作区 UTF-8 文本文件原子读写辅助方法。
 */
final class WorkspaceTextFiles {

    private WorkspaceTextFiles() {
    }

    /**
     * 严格按 UTF-8 读取文件。
     *
     * @param target 目标普通文件
     * @return 文件文本
     */
    static String readUtf8(Path target) {
        Set<OpenOption> options = Set.of(StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS);
        try (SeekableByteChannel channel = Files.newByteChannel(target, options);
             java.io.InputStream input = Channels.newInputStream(channel)) {
            byte[] bytes = input.readAllBytes();
            return decodeUtf8(bytes);
        } catch (IOException error) {
            throw new WorkspaceToolException("WORKSPACE_IO_FAILED",
                    "Workspace text file cannot be read.", true, error);
        }
    }

    /**
     * 以流式方式读取有界行范围，避免为了返回少量行先加载完整文本。
     *
     * @param target 目标普通文件
     * @param ranges 可选行范围，支持 `start,end` 与负数尾部索引
     * @return 带真实起始行号的行范围
     */
    static RangedLines readUtf8Lines(Path target, String ranges, long maxReadBytes) {
        ParsedRange range = ParsedRange.parse(ranges);
        Set<OpenOption> options = Set.of(StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS);
        try (SeekableByteChannel channel = Files.newByteChannel(target, options);
             BufferedReader reader = new BufferedReader(new InputStreamReader(
                     Channels.newInputStream(channel), StandardCharsets.UTF_8.newDecoder()
                     .onMalformedInput(CodingErrorAction.REPORT)
                     .onUnmappableCharacter(CodingErrorAction.REPORT)))) {
            if (range.negative()) {
                return readTail(reader, range, maxReadBytes);
            }
            List<String> selected = new ArrayList<>();
            long selectedBytes = 0;
            int lineNumber = 0;
            String line;
            while ((line = reader.readLine()) != null) {
                lineNumber++;
                if (lineNumber >= range.start() && lineNumber <= range.end()) {
                    selectedBytes = addLineBytes(selectedBytes, line, maxReadBytes);
                    selected.add(line);
                }
                if (lineNumber >= range.end()) {
                    break;
                }
            }
            if (lineNumber < range.start() && lineNumber > 0) {
                throw invalidRange("Invalid range: start line is outside the file.");
            }
            return new RangedLines(range.start(), selected, selectedBytes);
        } catch (CharacterCodingException error) {
            throw new WorkspaceToolException("FILE_ENCODING_INVALID",
                    "Workspace file is not valid UTF-8 text.", false, error);
        } catch (IOException error) {
            throw new WorkspaceToolException("WORKSPACE_IO_FAILED",
                    "Workspace text file cannot be read.", true, error);
        }
    }

    /**
     * 严格解码 UTF-8 字节。
     *
     * @param bytes 文本字节
     * @return UTF-8 文本
     */
    static String decodeUtf8(byte[] bytes) {
        try {
            CharBuffer decoded = StandardCharsets.UTF_8.newDecoder()
                    .onMalformedInput(CodingErrorAction.REPORT)
                    .onUnmappableCharacter(CodingErrorAction.REPORT)
                    .decode(ByteBuffer.wrap(bytes));
            return decoded.toString();
        } catch (CharacterCodingException error) {
            throw new WorkspaceToolException("FILE_ENCODING_INVALID",
                    "Workspace file is not valid UTF-8 text.", false, error);
        }
    }

    /**
     * 使用同目录临时文件原子替换目标内容。
     *
     * @param pathGuard 路径保护器
     * @param target 目标文件
     * @param bytes 新文件字节
     */
    static void atomicWrite(WorkspacePathGuard pathGuard, Path target, byte[] bytes) {
        pathGuard.ensureParentDirectories(target);
        Path parent = target.getParent();
        Path temporary = null;
        try {
            temporary = Files.createTempFile(parent, ".easyagents-write-", ".tmp");
            try (FileChannel channel = FileChannel.open(
                    temporary, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING)) {
                ByteBuffer buffer = ByteBuffer.wrap(bytes);
                while (buffer.hasRemaining()) {
                    channel.write(buffer);
                }
                channel.force(true);
            }
            pathGuard.revalidate(target);
            Files.move(temporary, target,
                    StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
            forceDirectory(parent);
        } catch (IOException error) {
            throw new WorkspaceToolException("WORKSPACE_IO_FAILED",
                    "Workspace text file cannot be committed.", true, error);
        } finally {
            if (temporary != null) {
                try {
                    Files.deleteIfExists(temporary);
                } catch (IOException ignored) {
                    // 提交失败已经向上抛出，临时文件清理失败由后续工作区清理任务兜底。
                }
            }
        }
    }

    private static RangedLines readTail(BufferedReader reader,
                                        ParsedRange range,
                                        long maxReadBytes) throws IOException {
        long requestedKeep = Math.max(Math.abs((long) range.start()), Math.abs((long) range.end()));
        if (requestedKeep > Math.min(maxReadBytes, 100_000L)) {
            throw new WorkspaceToolException("WORKSPACE_QUOTA_EXCEEDED",
                    "Requested tail range exceeds the configured read bound.", false);
        }
        int keep = Math.toIntExact(requestedKeep);
        Deque<String> tail = new ArrayDeque<>(keep);
        int lineCount = 0;
        String line;
        while ((line = reader.readLine()) != null) {
            lineCount++;
            if (tail.size() == keep) {
                tail.removeFirst();
            }
            long lineBytes = line.getBytes(StandardCharsets.UTF_8).length + 1L;
            if (lineBytes > maxReadBytes) {
                throw new WorkspaceToolException("WORKSPACE_QUOTA_EXCEEDED",
                        "Workspace range read exceeds max-read-size.", false);
            }
            tail.addLast(line);
        }
        if (lineCount == 0) {
            return new RangedLines(1, List.of(), 0);
        }
        int start = Math.max(1, lineCount + range.start() + 1);
        int end = Math.min(lineCount, lineCount + range.end() + 1);
        if (start > end) {
            throw invalidRange("Invalid range: start line is greater than end line.");
        }
        int retainedStart = lineCount - tail.size() + 1;
        List<String> retained = new ArrayList<>(tail);
        List<String> selected = new ArrayList<>(
                retained.subList(start - retainedStart, end - retainedStart + 1));
        long selectedBytes = 0;
        for (String selectedLine : selected) {
            selectedBytes = addLineBytes(selectedBytes, selectedLine, maxReadBytes);
        }
        return new RangedLines(start, selected, selectedBytes);
    }

    private static long addLineBytes(long current, String line, long maxReadBytes) {
        long updated;
        try {
            updated = Math.addExact(current, line.getBytes(StandardCharsets.UTF_8).length + 1L);
        } catch (ArithmeticException error) {
            throw new WorkspaceToolException("WORKSPACE_QUOTA_EXCEEDED",
                    "Workspace range read exceeds max-read-size.", false, error);
        }
        if (updated > maxReadBytes) {
            throw new WorkspaceToolException("WORKSPACE_QUOTA_EXCEEDED",
                    "Workspace range read exceeds max-read-size.", false);
        }
        return updated;
    }

    private static void forceDirectory(Path directory) {
        try (FileChannel channel = FileChannel.open(directory, StandardOpenOption.READ)) {
            channel.force(true);
        } catch (IOException | UnsupportedOperationException ignored) {
            // 某些文件系统不支持目录 fsync；文件内容和原子 rename 已经完成。
        }
    }

    private static WorkspaceToolException invalidRange(String message) {
        return new WorkspaceToolException("INVALID_ARGUMENT", message, false);
    }

    /**
     * 流式读取结果。
     *
     * @param startLine 第一行真实 1-based 行号
     * @param lines 文本行
     * @param readBytes 返回文本字节数
     */
    record RangedLines(int startLine, List<String> lines, long readBytes) {
    }

    /**
     * 归一化行范围。
     *
     * @param start 起始行，允许负数
     * @param end 结束行，允许负数
     * @param negative 是否为尾部范围
     */
    private record ParsedRange(int start, int end, boolean negative) {

        private static ParsedRange parse(String ranges) {
            if (ranges == null || ranges.isBlank()) {
                return new ParsedRange(1, Integer.MAX_VALUE, false);
            }
            String normalized = ranges.trim().replace("[", "").replace("]", "");
            String[] parts = normalized.split(",", -1);
            if (parts.length != 2) {
                throw invalidRange("Invalid range format. Expected 'start,end'.");
            }
            try {
                int start = Integer.parseInt(parts[0].trim());
                int end = Integer.parseInt(parts[1].trim());
                if (start == 0 || end == 0 || (start < 0) != (end < 0)) {
                    throw invalidRange("Invalid range: use either positive or negative line numbers.");
                }
                if (start > end) {
                    throw invalidRange("Invalid range: start line is greater than end line.");
                }
                return new ParsedRange(start, end, start < 0);
            } catch (NumberFormatException error) {
                throw new WorkspaceToolException("INVALID_ARGUMENT",
                        "Invalid range format. Expected integer line numbers.", false, error);
            }
        }
    }
}
