package track4

import (
	"bytes"
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"net/http"
	"net/http/httptest"

	"github.com/stretchr/testify/require"
)

type stubRoleManager struct {
	allowed bool
	gotIP   string
	logs    int
}

func (s *stubRoleManager) IsIPWhitelisted(_ context.Context, _ string, ipAddress string) (bool, error) {
	s.gotIP = ipAddress
	return s.allowed, nil
}

func (s *stubRoleManager) LogOperatorEvent(_ context.Context, _ string, _ *int, _ string, _ string, _ string, _ map[string]interface{}, _ string, _ string) error {
	s.logs++
	return nil
}

func TestHandleRunScriptUsesForwardedClientIPAndRunsAllowlistedScript(t *testing.T) {
	root := t.TempDir()
	scriptPath := filepath.Join(root, "echo.sh")
	require.NoError(t, os.WriteFile(scriptPath, []byte("#!/usr/bin/env bash\necho hello \"$1\"\n"), 0o644))

	t.Setenv("OPERATOR_SCRIPTS_ROOT", root)
	t.Setenv("OPERATOR_SCRIPT_ALLOWLIST", "echo.sh")
	t.Setenv("OPERATOR_SCRIPT_TIMEOUT_SEC", "30")
	t.Setenv("TRUST_PROXY_CIDRS", "10.0.0.0/8")

	roleMgr := &stubRoleManager{allowed: true}
	s := &Server{roleMgr: roleMgr, chainID: 138}

	reqBody := []byte(`{"script":"echo.sh","args":["world"]}`)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/track4/operator/run-script", bytes.NewReader(reqBody))
	req = req.WithContext(context.WithValue(req.Context(), "user_address", "0x4A666F96fC8764181194447A7dFdb7d471b301C8"))
	req.RemoteAddr = "10.0.0.10:8080"
	req.Header.Set("X-Forwarded-For", "203.0.113.9, 10.0.0.10")
	w := httptest.NewRecorder()

	s.HandleRunScript(w, req)

	require.Equal(t, http.StatusOK, w.Code)
	require.Equal(t, "203.0.113.9", roleMgr.gotIP)
	require.Equal(t, 2, roleMgr.logs)

	var out struct {
		Data map[string]any `json:"data"`
	}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out))
	require.Equal(t, "echo.sh", out.Data["script"])
	require.Equal(t, float64(0), out.Data["exit_code"])
	require.Equal(t, "hello world", out.Data["stdout"])
	require.Equal(t, false, out.Data["timed_out"])
}

func TestHandleRunScriptRejectsNonAllowlistedScript(t *testing.T) {
	root := t.TempDir()
	require.NoError(t, os.WriteFile(filepath.Join(root, "allowed.sh"), []byte("#!/usr/bin/env bash\necho ok\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(root, "blocked.sh"), []byte("#!/usr/bin/env bash\necho blocked\n"), 0o644))

	t.Setenv("OPERATOR_SCRIPTS_ROOT", root)
	t.Setenv("OPERATOR_SCRIPT_ALLOWLIST", "allowed.sh")

	s := &Server{roleMgr: &stubRoleManager{allowed: true}, chainID: 138}

	req := httptest.NewRequest(http.MethodPost, "/api/v1/track4/operator/run-script", bytes.NewReader([]byte(`{"script":"blocked.sh"}`)))
	req = req.WithContext(context.WithValue(req.Context(), "user_address", "0x4A666F96fC8764181194447A7dFdb7d471b301C8"))
	req.RemoteAddr = "127.0.0.1:9999"
	w := httptest.NewRecorder()

	s.HandleRunScript(w, req)

	require.Equal(t, http.StatusForbidden, w.Code)
	require.Contains(t, w.Body.String(), "script not in OPERATOR_SCRIPT_ALLOWLIST")
}

func TestHandleRunScriptRejectsFilenameCollisionOutsideAllowlistedPath(t *testing.T) {
	root := t.TempDir()
	require.NoError(t, os.MkdirAll(filepath.Join(root, "safe"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(root, "unsafe"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(root, "safe", "backup.sh"), []byte("#!/usr/bin/env bash\necho safe\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(root, "unsafe", "backup.sh"), []byte("#!/usr/bin/env bash\necho unsafe\n"), 0o644))

	t.Setenv("OPERATOR_SCRIPTS_ROOT", root)
	t.Setenv("OPERATOR_SCRIPT_ALLOWLIST", "safe/backup.sh")

	s := &Server{roleMgr: &stubRoleManager{allowed: true}, chainID: 138}

	req := httptest.NewRequest(http.MethodPost, "/api/v1/track4/operator/run-script", bytes.NewReader([]byte(`{"script":"unsafe/backup.sh"}`)))
	req = req.WithContext(context.WithValue(req.Context(), "user_address", "0x4A666F96fC8764181194447A7dFdb7d471b301C8"))
	req.RemoteAddr = "127.0.0.1:9999"
	w := httptest.NewRecorder()

	s.HandleRunScript(w, req)

	require.Equal(t, http.StatusForbidden, w.Code)
	require.Contains(t, w.Body.String(), "script not in OPERATOR_SCRIPT_ALLOWLIST")
}

func TestHandleRunScriptTruncatesLargeOutput(t *testing.T) {
	root := t.TempDir()
	scriptPath := filepath.Join(root, "large.sh")
	require.NoError(t, os.WriteFile(scriptPath, []byte("#!/usr/bin/env bash\npython3 - <<'PY'\nprint('x' * 70000)\nPY\n"), 0o644))

	t.Setenv("OPERATOR_SCRIPTS_ROOT", root)
	t.Setenv("OPERATOR_SCRIPT_ALLOWLIST", "large.sh")
	t.Setenv("OPERATOR_SCRIPT_TIMEOUT_SEC", "30")

	s := &Server{roleMgr: &stubRoleManager{allowed: true}, chainID: 138}

	req := httptest.NewRequest(http.MethodPost, "/api/v1/track4/operator/run-script", bytes.NewReader([]byte(`{"script":"large.sh"}`)))
	req = req.WithContext(context.WithValue(req.Context(), "user_address", "0x4A666F96fC8764181194447A7dFdb7d471b301C8"))
	req.RemoteAddr = "127.0.0.1:9999"
	w := httptest.NewRecorder()

	s.HandleRunScript(w, req)

	require.Equal(t, http.StatusOK, w.Code)

	var out struct {
		Data struct {
			ExitCode        float64 `json:"exit_code"`
			Stdout          string  `json:"stdout"`
			StdoutTruncated bool    `json:"stdout_truncated"`
		} `json:"data"`
	}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out))
	require.Equal(t, float64(0), out.Data.ExitCode)
	require.True(t, out.Data.StdoutTruncated)
	require.Contains(t, out.Data.Stdout, "[truncated after")
	require.LessOrEqual(t, len(out.Data.Stdout), maxOperatorScriptOutputBytes+64)
}
