package rest

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"

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

func resetMissionControlTestGlobals() {
	liquidityPoolsCache = sync.Map{}
	registryOnce = sync.Once{}
	registryAddrToKey = nil
	registryLoadErr = nil
}

func TestHandleMissionControlLiquidityTokenPathRequiresEnv(t *testing.T) {
	resetMissionControlTestGlobals()
	t.Setenv("TOKEN_AGGREGATION_BASE_URL", "")
	t.Setenv("TOKEN_AGGREGATION_URL", "")

	s := NewServer(nil, 138)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools", nil)
	w := httptest.NewRecorder()

	s.handleMissionControlLiquidityTokenPath(w, req)

	require.Equal(t, http.StatusServiceUnavailable, w.Code)
	require.Contains(t, w.Body.String(), "TOKEN_AGGREGATION_BASE_URL not configured")
}

func TestHandleMissionControlLiquidityTokenPathCachesSuccess(t *testing.T) {
	resetMissionControlTestGlobals()

	var hitCount int
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hitCount++
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":{"count":1,"pools":[]}}`))
	}))
	defer upstream.Close()

	t.Setenv("TOKEN_AGGREGATION_BASE_URL", upstream.URL)
	t.Setenv("CHAIN_ID", "138")

	s := NewServer(nil, 138)
	path := "/api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools"

	w1 := httptest.NewRecorder()
	s.handleMissionControlLiquidityTokenPath(w1, httptest.NewRequest(http.MethodGet, path, nil))
	require.Equal(t, http.StatusOK, w1.Code)
	require.Equal(t, "miss", w1.Header().Get("X-Mission-Control-Cache"))

	w2 := httptest.NewRecorder()
	s.handleMissionControlLiquidityTokenPath(w2, httptest.NewRequest(http.MethodGet, path, nil))
	require.Equal(t, http.StatusOK, w2.Code)
	require.Equal(t, "hit", w2.Header().Get("X-Mission-Control-Cache"))

	require.Equal(t, 1, hitCount, "second request should be served from the in-memory cache")
	require.JSONEq(t, w1.Body.String(), w2.Body.String())
}

func TestHandleMissionControlLiquidityTokenPathBypassesCacheWhenRequested(t *testing.T) {
	resetMissionControlTestGlobals()

	var hitCount int
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hitCount++
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"data":{"count":1,"pools":[]}}`))
	}))
	defer upstream.Close()

	t.Setenv("TOKEN_AGGREGATION_BASE_URL", upstream.URL)
	t.Setenv("CHAIN_ID", "138")

	s := NewServer(nil, 138)
	path := "/api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools"

	w1 := httptest.NewRecorder()
	s.handleMissionControlLiquidityTokenPath(w1, httptest.NewRequest(http.MethodGet, path, nil))
	require.Equal(t, http.StatusOK, w1.Code)
	require.Equal(t, "miss", w1.Header().Get("X-Mission-Control-Cache"))

	req2 := httptest.NewRequest(http.MethodGet, path+"?refresh=1", nil)
	req2.Header.Set("Cache-Control", "no-cache")
	w2 := httptest.NewRecorder()
	s.handleMissionControlLiquidityTokenPath(w2, req2)
	require.Equal(t, http.StatusOK, w2.Code)
	require.Equal(t, "bypass", w2.Header().Get("X-Mission-Control-Cache"))

	require.Equal(t, 2, hitCount, "refresh=1 should force a fresh upstream read")
}

func TestHandleMissionControlLiquidityTokenPathTreatsGenericSuccessErrorEnvelopeAsBadGateway(t *testing.T) {
	resetMissionControlTestGlobals()

	var hitCount int
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		hitCount++
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"error":"Internal server error"}`))
	}))
	defer upstream.Close()

	t.Setenv("TOKEN_AGGREGATION_BASE_URL", upstream.URL)
	t.Setenv("CHAIN_ID", "138")

	s := NewServer(nil, 138)
	path := "/api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools"

	w1 := httptest.NewRecorder()
	s.handleMissionControlLiquidityTokenPath(w1, httptest.NewRequest(http.MethodGet, path, nil))
	require.Equal(t, http.StatusBadGateway, w1.Code)
	require.Equal(t, "miss", w1.Header().Get("X-Mission-Control-Cache"))
	require.JSONEq(t, `{"error":"Internal server error"}`, w1.Body.String())

	w2 := httptest.NewRecorder()
	s.handleMissionControlLiquidityTokenPath(w2, httptest.NewRequest(http.MethodGet, path, nil))
	require.Equal(t, http.StatusBadGateway, w2.Code)
	require.Equal(t, "miss", w2.Header().Get("X-Mission-Control-Cache"))
	require.Equal(t, 2, hitCount, "generic error envelopes must not be cached as success")
}

func TestHandleMissionControlBridgeTraceLabelsFromRegistry(t *testing.T) {
	resetMissionControlTestGlobals()

	fromAddr := "0x1111111111111111111111111111111111111111"
	toAddr := "0x2222222222222222222222222222222222222222"

	registryJSON := `{
	  "chains": {
	    "138": {
	      "contracts": {
	        "CHAIN138_SOURCE_BRIDGE": "` + fromAddr + `",
	        "CHAIN138_DEST_BRIDGE": "` + toAddr + `"
	      }
	    }
	  }
	}`
	registryPath := filepath.Join(t.TempDir(), "smart-contracts-master.json")
	require.NoError(t, os.WriteFile(registryPath, []byte(registryJSON), 0o644))

	blockscout := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/api/v2/transactions/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", r.URL.Path)
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{
		  "from": {"hash":"` + fromAddr + `"},
		  "to": {"hash":"` + toAddr + `"}
		}`))
	}))
	defer blockscout.Close()

	t.Setenv("SMART_CONTRACTS_MASTER_JSON", registryPath)
	t.Setenv("BLOCKSCOUT_INTERNAL_URL", blockscout.URL)
	t.Setenv("EXPLORER_PUBLIC_BASE", "https://explorer.example.org")

	s := NewServer(nil, 138)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/bridge/trace?tx=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", nil)
	w := httptest.NewRecorder()

	s.HandleMissionControlBridgeTrace(w, req)

	require.Equal(t, http.StatusOK, w.Code)
	var out struct {
		Data map[string]any `json:"data"`
	}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out))
	require.Equal(t, strings.ToLower(fromAddr), out.Data["from"])
	require.Equal(t, strings.ToLower(toAddr), out.Data["to"])
	require.Equal(t, "CHAIN138_SOURCE_BRIDGE", out.Data["from_registry"])
	require.Equal(t, "CHAIN138_DEST_BRIDGE", out.Data["to_registry"])
	require.Equal(t, "https://explorer.example.org/transactions/0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", out.Data["blockscout_url"])
}

func TestHandleMissionControlBridgeTraceFallsBackToAddressInventoryLabels(t *testing.T) {
	resetMissionControlTestGlobals()

	fromAddr := "0x4A666F96fC8764181194447A7dFdb7d471b301C8"
	toAddr := "0x152ed3e9912161b76bdfd368d0c84b7c31c10de7"

	tempDir := t.TempDir()
	registryPath := filepath.Join(tempDir, "smart-contracts-master.json")
	inventoryPath := filepath.Join(tempDir, "address-inventory.json")

	require.NoError(t, os.WriteFile(registryPath, []byte(`{
	  "chains": {
	    "138": {
	      "contracts": {
	        "CCIP_Router": "0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817"
	      }
	    }
	  }
	}`), 0o644))
	require.NoError(t, os.WriteFile(inventoryPath, []byte(`{
	  "inventory": {
	    "DEPLOYER_ADMIN_138": "`+fromAddr+`",
	    "CW_L1_BRIDGE_CHAIN138": "`+toAddr+`"
	  }
	}`), 0o644))

	blockscout := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{
		  "from": {"hash":"` + fromAddr + `"},
		  "to": {"hash":"` + toAddr + `"}
		}`))
	}))
	defer blockscout.Close()

	t.Setenv("SMART_CONTRACTS_MASTER_JSON", registryPath)
	t.Setenv("EXPLORER_ADDRESS_INVENTORY_FILE", inventoryPath)
	t.Setenv("BLOCKSCOUT_INTERNAL_URL", blockscout.URL)

	s := NewServer(nil, 138)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/bridge/trace?tx=0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", nil)
	w := httptest.NewRecorder()

	s.HandleMissionControlBridgeTrace(w, req)

	require.Equal(t, http.StatusOK, w.Code)
	var out struct {
		Data map[string]any `json:"data"`
	}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &out))
	require.Equal(t, strings.ToLower(fromAddr), out.Data["from"])
	require.Equal(t, strings.ToLower(toAddr), out.Data["to"])
	require.Equal(t, "DEPLOYER_ADMIN_138", out.Data["from_registry"])
	require.Equal(t, "CW_L1_BRIDGE_CHAIN138", out.Data["to_registry"])
}

func TestHandleMissionControlBridgeTraceRejectsBadHash(t *testing.T) {
	resetMissionControlTestGlobals()
	s := NewServer(nil, 138)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/mission-control/bridge/trace?tx=not-a-tx", nil)
	w := httptest.NewRecorder()

	s.HandleMissionControlBridgeTrace(w, req)

	require.Equal(t, http.StatusBadRequest, w.Code)
	require.Contains(t, w.Body.String(), "invalid transaction hash")
}
