package com.github.catatafishen.agentbridge.psi.tools.file;

import com.github.catatafishen.agentbridge.psi.ToolError;
import com.github.catatafishen.agentbridge.psi.ToolLayerSettings;
import com.github.catatafishen.agentbridge.psi.ToolUtils;
import com.google.gson.JsonObject;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import com.intellij.util.ui.UIUtil;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

/**
 * Platform tests for {@link DeleteFileTool}.
 *
 * <p>JUnit 3 style (extends BasePlatformTestCase): test methods must be {@code public void testXxx()}.
 * Run via Gradle only: {@code ./gradlew :plugin-core:test}.
 *
 * <p><b>File creation note:</b> {@code myFixture.addFileToProject()} creates in-memory VFS files
 * that are invisible to {@code LocalFileSystem#findFileByPath}. Tests use real disk files created
 * under a temp directory, registered in the VFS via {@code LocalFileSystem#refreshAndFindFileByPath}.
 * This ensures {@code DeleteFileTool}'s internal {@code resolveVirtualFile} call can locate them.
 *
 * <p><b>Async execution note:</b> {@code DeleteFileTool} uses {@code ReadAction.nonBlocking} to
 * resolve the target in smart mode, then {@code EdtUtil.invokeLater} to schedule the actual
 * {@code WriteAction} deletion back onto the EDT. The {@code executeSync} helper runs
 * {@code execute()} on a pooled thread while pumping the EDT queue, ensuring both the async
 * resolution and the write-action callback can complete without deadlocking the test.
 */
public class DeleteFileToolTest extends BasePlatformTestCase {

    private DeleteFileTool tool;
    private Path tempDir;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        // Prevent followFileIfEnabled from opening editors during tests.
        PropertiesComponent.getInstance(getProject())
            .setValue(ToolLayerSettings.FOLLOW_AGENT_FILES_KEY, "false");
        tool = new DeleteFileTool(getProject());
        tempDir = Files.createTempDirectory("delete-file-tool-test");
    }

    @Override
    protected void tearDown() throws Exception {
        try {
            FileEditorManager fem = FileEditorManager.getInstance(getProject());
            for (VirtualFile openFile : fem.getOpenFiles()) {
                fem.closeFile(openFile);
            }
            deleteDir(tempDir);
        } finally {
            super.tearDown();
        }
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    /**
     * Creates a real file on disk and registers it in the VFS so that
     * {@code LocalFileSystem#findFileByPath} can find it during {@code execute()}.
     */
    private VirtualFile createTestFile(String name, String content) {
        try {
            Path file = tempDir.resolve(name);
            Files.writeString(file, content);
            VirtualFile vf = LocalFileSystem.getInstance().refreshAndFindFileByPath(file.toString());
            assertNotNull("Failed to register test file in VFS: " + file, vf);
            return vf;
        } catch (IOException e) {
            throw new RuntimeException("Failed to create test file: " + name, e);
        }
    }

    private static void deleteDir(Path dir) {
        if (dir == null || !Files.exists(dir)) return;
        try (Stream<Path> walk = Files.walk(dir)) {
            walk.sorted(Comparator.reverseOrder()).forEach(p -> {
                try {
                    Files.deleteIfExists(p);
                } catch (IOException ignored) {
                    // best-effort cleanup
                }
            });
        } catch (IOException ignored) {
            // best-effort cleanup
        }
    }

    /**
     * Builds a {@link JsonObject} from alternating key/value string pairs.
     */
    private static JsonObject args(String... pairs) {
        JsonObject obj = new JsonObject();
        for (int i = 0; i < pairs.length; i += 2) {
            obj.addProperty(pairs[i], pairs[i + 1]);
        }
        return obj;
    }

    /**
     * Runs {@code tool.execute(argsObj)} on a pooled thread while pumping the EDT queue.
     * Required because {@code DeleteFileTool} schedules its write action back onto the EDT
     * via {@code EdtUtil.invokeLater}; blocking the EDT directly would deadlock.
     */
    private String executeSync(JsonObject argsObj) throws Exception {
        CompletableFuture<String> future = new CompletableFuture<>();
        ApplicationManager.getApplication().executeOnPooledThread(() -> {
            try {
                future.complete(tool.execute(argsObj));
            } catch (Exception e) {
                future.completeExceptionally(e);
            }
        });
        long deadline = System.currentTimeMillis() + 15_000;
        while (!future.isDone()) {
            UIUtil.dispatchAllInvocationEvents();
            if (System.currentTimeMillis() > deadline) {
                fail("tool.execute() timed out after 15 seconds");
            }
        }
        return future.get();
    }

    // ── Tests ─────────────────────────────────────────────────────────────────

    /**
     * Deleting an existing, VFS-registered file should return a response that starts with
     * "Deleted file:".
     */
    public void testDeleteExistingFile() throws Exception {
        VirtualFile vf = createTestFile("todelete.txt", "some content");

        String result = executeSync(args("path", vf.getPath()));

        assertTrue("Expected 'Deleted file:' message, got: " + result,
            result.startsWith("Deleted file:"));
        assertTrue("Result should contain the deleted path, got: " + result,
            result.contains(vf.getPath()));
    }

    /**
     * Attempting to delete a path that does not exist on disk (or in the VFS) should
     * return an error containing the standard "File not found:" message.
     */
    public void testDeleteNonExistentFile() throws Exception {
        String nonExistentPath = tempDir.resolve("does-not-exist.txt").toString();

        String result = executeSync(args("path", nonExistentPath));

        assertTrue("Expected error, got: " + result,
            ToolError.isError(result));
        assertTrue("Expected 'File not found' in error, got: " + result,
            result.contains("FILE_NOT_FOUND") || result.contains(ToolUtils.ERROR_FILE_NOT_FOUND));
    }

    /**
     * Omitting the "path" parameter should return the standard path-required error.
     * The tool returns synchronously before any async code, so calling execute() directly
     * from the EDT test thread is safe.
     */
    public void testDeleteWithoutPath() throws Exception {
        // Missing path — returns before any ReadAction.nonBlocking or EdtUtil.invokeLater.
        String result = tool.execute(new JsonObject());

        assertEquals(ToolUtils.ERROR_PATH_REQUIRED, result);
    }

    /**
     * After a successful delete, the file must no longer exist on disk.
     */
    public void testFileIsActuallyDeleted() throws Exception {
        VirtualFile vf = createTestFile("willbedeleted.txt", "delete me");
        String path = vf.getPath();
        assertTrue("File should exist on disk before deletion", Files.exists(Path.of(path)));

        executeSync(args("path", path));

        assertFalse("File should not exist on disk after deletion", Files.exists(Path.of(path)));
    }
}
