package conversion

import (
	"encoding/json"
	"errors"
	"testing"

	"nex/backend/internal/conversion/canonical"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
)

func TestConversionError_WithProviderProtocol(t *testing.T) {
	err := NewConversionError(ErrorCodeInvalidInput, "test").WithProviderProtocol("anthropic")
	assert.Equal(t, "anthropic", err.ProviderProtocol)
}

func TestConversionError_WithInterfaceType(t *testing.T) {
	err := NewConversionError(ErrorCodeInvalidInput, "test").WithInterfaceType("CHAT")
	assert.Equal(t, "CHAT", err.InterfaceType)
}

func TestConversionError_FullBuilder(t *testing.T) {
	err := NewConversionError(ErrorCodeInvalidInput, "bad").
		WithClientProtocol("openai").
		WithProviderProtocol("anthropic").
		WithInterfaceType("CHAT").
		WithDetail("field", "model").
		WithCause(errors.New("root"))

	assert.Equal(t, ErrorCodeInvalidInput, err.Code)
	assert.Equal(t, "openai", err.ClientProtocol)
	assert.Equal(t, "anthropic", err.ProviderProtocol)
	assert.Equal(t, "CHAT", err.InterfaceType)
	assert.Equal(t, "model", err.Details["field"])
	assert.Equal(t, "root", err.Cause.Error())
}

func TestEngine_Use(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())
	called := false
	engine.Use(&testMiddleware{fn: func(req *canonical.CanonicalRequest, cp, pp string, ctx *ConversionContext) (*canonical.CanonicalRequest, error) {
		called = true
		return req, nil
	}})

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.decodeReqFn = func(raw []byte) (*canonical.CanonicalRequest, error) {
		return &canonical.CanonicalRequest{Model: "test"}, nil
	}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.encodeReqFn = func(req *canonical.CanonicalRequest, p *TargetProvider) ([]byte, error) {
		return json.Marshal(req)
	}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	_, err := engine.ConvertHttpRequest(HTTPRequestSpec{
		URL: "/chat/completions", Method: "POST", Body: []byte(`{}`),
	}, "client", "provider", NewTargetProvider("https://example.com", "key", "model"))
	require.NoError(t, err)
	assert.True(t, called)
}

func TestConvertHttpRequest_DecodeError(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())
	clientAdapter := newMockAdapter("client", false)
	clientAdapter.decodeReqFn = func(raw []byte) (*canonical.CanonicalRequest, error) {
		return nil, errors.New("decode failed")
	}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(newMockAdapter("provider", false))

	_, err := engine.ConvertHttpRequest(HTTPRequestSpec{
		URL: "/chat/completions", Method: "POST", Body: []byte(`{}`),
	}, "client", "provider", NewTargetProvider("", "", ""))
	assert.Error(t, err)
}

func TestConvertHttpRequest_EncodeError(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())
	_ = engine.RegisterAdapter(newMockAdapter("client", false))
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.encodeReqFn = func(req *canonical.CanonicalRequest, p *TargetProvider) ([]byte, error) {
		return nil, errors.New("encode failed")
	}
	_ = engine.RegisterAdapter(providerAdapter)

	_, err := engine.ConvertHttpRequest(HTTPRequestSpec{
		URL: "/chat/completions", Method: "POST", Body: []byte(`{}`),
	}, "client", "provider", NewTargetProvider("", "", ""))
	assert.Error(t, err)
}

func TestConvertHttpResponse_CrossProtocol(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.encodeRespFn = func(resp *canonical.CanonicalResponse) ([]byte, error) {
		return json.Marshal(map[string]string{"id": resp.ID})
	}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.decodeRespFn = func(raw []byte) (*canonical.CanonicalResponse, error) {
		return &canonical.CanonicalResponse{ID: "resp-1", Model: "test"}, nil
	}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	result, err := engine.ConvertHttpResponse(HTTPResponseSpec{
		StatusCode: 200, Body: []byte(`{"id":"resp-1"}`),
	}, "client", "provider", InterfaceTypeChat, "")
	require.NoError(t, err)
	assert.Equal(t, 200, result.StatusCode)
	assert.Contains(t, string(result.Body), "resp-1")
}

func TestConvertHttpResponse_DecodeError(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.decodeRespFn = func(raw []byte) (*canonical.CanonicalResponse, error) {
		return nil, errors.New("decode error")
	}
	_ = engine.RegisterAdapter(providerAdapter)
	_ = engine.RegisterAdapter(newMockAdapter("client", false))

	_, err := engine.ConvertHttpResponse(HTTPResponseSpec{Body: []byte(`{}`)}, "client", "provider", InterfaceTypeChat, "")
	assert.Error(t, err)
}

func TestConvertHttpRequest_EmbeddingInterface(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.ifaceType = InterfaceTypeEmbeddings
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeEmbeddings: true}
	clientAdapter.decodeReqFn = func(raw []byte) (*canonical.CanonicalRequest, error) {
		return &canonical.CanonicalRequest{Model: "test"}, nil
	}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.ifaceType = InterfaceTypeEmbeddings
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeEmbeddings: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	result, err := engine.ConvertHttpRequest(HTTPRequestSpec{
		URL: "/v1/embeddings", Method: "POST", Body: []byte(`{"model":"text-embedding","input":"hello"}`),
	}, "client", "provider", NewTargetProvider("https://example.com", "key", "model"))
	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestConvertHttpRequest_RerankInterface(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.ifaceType = InterfaceTypeRerank
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeRerank: true}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.ifaceType = InterfaceTypeRerank
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeRerank: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	result, err := engine.ConvertHttpRequest(HTTPRequestSpec{
		URL: "/v1/rerank", Method: "POST", Body: []byte(`{"model":"rerank","query":"test","documents":["a"]}`),
	}, "client", "provider", NewTargetProvider("https://example.com", "key", "model"))
	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestConvertHttpResponse_EmbeddingInterface(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeEmbeddings: true}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeEmbeddings: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	result, err := engine.ConvertHttpResponse(HTTPResponseSpec{
		StatusCode: 200, Body: []byte(`{"object":"list","data":[],"model":"test"}`),
	}, "client", "provider", InterfaceTypeEmbeddings, "")
	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestConvertHttpResponse_RerankInterface(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeRerank: true}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeRerank: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	result, err := engine.ConvertHttpResponse(HTTPResponseSpec{
		StatusCode: 200, Body: []byte(`{"results":[],"model":"test"}`),
	}, "client", "provider", InterfaceTypeRerank, "")
	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestConvertHttpRequest_ModelsInterface_Passthrough(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())
	clientAdapter := newMockAdapter("client", false)
	clientAdapter.ifaceType = InterfaceTypeModels
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.ifaceType = InterfaceTypeModels
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	body := []byte(`{"object":"list","data":[]}`)
	result, err := engine.ConvertHttpRequest(HTTPRequestSpec{
		URL: "/models", Method: "GET", Body: body,
	}, "client", "provider", NewTargetProvider("https://example.com", "key", ""))
	require.NoError(t, err)
	assert.Equal(t, body, result.Body)
}

func TestConvertHttpResponse_ModelsInterface(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())
	clientAdapter := newMockAdapter("client", false)
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeModels: true}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeModels: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	result, err := engine.ConvertHttpResponse(HTTPResponseSpec{
		StatusCode: 200, Body: []byte(`{"object":"list","data":[]}`),
	}, "client", "provider", InterfaceTypeModels, "")
	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestConvertHttpResponse_ModelInfoInterface(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())
	clientAdapter := newMockAdapter("client", false)
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeModelInfo: true}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeModelInfo: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	result, err := engine.ConvertHttpResponse(HTTPResponseSpec{
		StatusCode: 200, Body: []byte(`{"id":"gpt-4","object":"model"}`),
	}, "client", "provider", InterfaceTypeModelInfo, "")
	require.NoError(t, err)
	assert.NotNil(t, result)
}

func TestRegistry_ListProtocols(t *testing.T) {
	registry := NewMemoryRegistry()
	_ = registry.Register(newMockAdapter("openai", true))
	_ = registry.Register(newMockAdapter("anthropic", true))

	protocols := registry.ListProtocols()
	assert.Len(t, protocols, 2)
	assert.Contains(t, protocols, "openai")
	assert.Contains(t, protocols, "anthropic")
}

func TestRegistry_ConcurrentAccess(t *testing.T) {
	registry := NewMemoryRegistry()
	done := make(chan bool, 2)

	go func() {
		for i := 0; i < 100; i++ {
			_ = registry.Register(newMockAdapter("proto-"+string(rune(i)), true))
		}
		done <- true
	}()

	go func() {
		for i := 0; i < 100; i++ {
			_, _ = registry.Get("proto-" + string(rune(i)))
		}
		_ = registry.ListProtocols()
		done <- true
	}()

	<-done
	<-done
}

func TestNewConversionContext(t *testing.T) {
	ctx := NewConversionContext(InterfaceTypeChat)
	assert.NotEmpty(t, ctx.ConversionID)
	assert.Equal(t, InterfaceTypeChat, ctx.InterfaceType)
	assert.NotNil(t, ctx.Metadata)
}

type testMiddleware struct {
	fn func(req *canonical.CanonicalRequest, clientProtocol, providerProtocol string, ctx *ConversionContext) (*canonical.CanonicalRequest, error)
}

func (m *testMiddleware) Intercept(req *canonical.CanonicalRequest, clientProtocol, providerProtocol string, ctx *ConversionContext) (*canonical.CanonicalRequest, error) {
	if m.fn != nil {
		return m.fn(req, clientProtocol, providerProtocol, ctx)
	}
	return req, nil
}

func (m *testMiddleware) InterceptStreamEvent(event *canonical.CanonicalStreamEvent, clientProtocol, providerProtocol string, ctx *ConversionContext) (*canonical.CanonicalStreamEvent, error) {
	return event, nil
}

var _ = json.Marshal

func TestConvertEmbeddingBody_DecodeError(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.decodeEmbeddingReqFn = func(raw []byte) (*canonical.CanonicalEmbeddingRequest, error) {
		return nil, errors.New("decode embedding failed")
	}
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeEmbeddings: true}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeEmbeddings: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	body := []byte(`{"model":"text-embedding","input":"hello"}`)
	result, err := engine.convertEmbeddingBody(clientAdapter, providerAdapter, NewTargetProvider("", "", ""), body)
	require.NoError(t, err)
	assert.Equal(t, body, result)
}

func TestConvertRerankBody_DecodeError(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	clientAdapter.decodeRerankReqFn = func(raw []byte) (*canonical.CanonicalRerankRequest, error) {
		return nil, errors.New("decode rerank failed")
	}
	clientAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeRerank: true}
	providerAdapter := newMockAdapter("provider", false)
	providerAdapter.supportsIface = map[InterfaceType]bool{InterfaceTypeRerank: true}
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	body := []byte(`{"model":"rerank","query":"test","documents":["a"]}`)
	result, err := engine.convertRerankBody(clientAdapter, providerAdapter, NewTargetProvider("", "", ""), body)
	require.NoError(t, err)
	assert.Equal(t, body, result)
}

func TestConvertBody_UnknownInterfaceType(t *testing.T) {
	registry := NewMemoryRegistry()
	engine := NewConversionEngine(registry, zap.NewNop())

	clientAdapter := newMockAdapter("client", false)
	providerAdapter := newMockAdapter("provider", false)
	_ = engine.RegisterAdapter(clientAdapter)
	_ = engine.RegisterAdapter(providerAdapter)

	body := []byte(`{"test":"data"}`)
	result, err := engine.convertBody(InterfaceType("UNKNOWN"), clientAdapter, providerAdapter, NewTargetProvider("", "", ""), body)
	require.NoError(t, err)
	assert.Equal(t, body, result)
}
