package integration_test

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"sub2api-cn-relay-manager/internal/host/sub2api"
)

func TestSub2APIHostAdapterGetHostVersion(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"), sub2api.WithBearerToken("bearer-token"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	version, err := client.GetHostVersion(context.Background())
	if err != nil {
		t.Fatalf("GetHostVersion() error = %v", err)
	}

	if version != "0.1.126" {
		t.Fatalf("version = %q, want %q", version, "0.1.126")
	}
}

func TestSub2APIHostAdapterProbeCapabilities(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	caps, err := client.ProbeCapabilities(context.Background())
	if err != nil {
		t.Fatalf("ProbeCapabilities() error = %v", err)
	}

	if !caps.Groups || !caps.Channels || !caps.Plans || !caps.Accounts || !caps.AccountTest || !caps.AccountModels || !caps.Subscriptions {
		t.Fatalf("ProbeCapabilities() = %+v, want all capabilities true", caps)
	}
}

func TestSub2APIHostAdapterMapsUnauthorizedErrors(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL)
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	_, err = client.GetHostVersion(context.Background())
	if err == nil {
		t.Fatal("GetHostVersion() error = nil, want 401 error")
	}

	var httpErr *sub2api.HTTPError
	if !errors.As(err, &httpErr) {
		t.Fatalf("GetHostVersion() error type = %T, want *sub2api.HTTPError", err)
	}

	if httpErr.StatusCode != http.StatusUnauthorized {
		t.Fatalf("StatusCode = %d, want %d", httpErr.StatusCode, http.StatusUnauthorized)
	}
}

func TestSub2APIHostAdapterMapsNotFoundErrors(t *testing.T) {
	server := httptest.NewServer(http.NotFoundHandler())
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	_, err = client.GetHostVersion(context.Background())
	if err == nil {
		t.Fatal("GetHostVersion() error = nil, want 404 error")
	}

	var httpErr *sub2api.HTTPError
	if !errors.As(err, &httpErr) {
		t.Fatalf("GetHostVersion() error type = %T, want *sub2api.HTTPError", err)
	}

	if httpErr.StatusCode != http.StatusNotFound {
		t.Fatalf("StatusCode = %d, want %d", httpErr.StatusCode, http.StatusNotFound)
	}
}

func TestSub2APIHostAdapterCreateGroup(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"), sub2api.WithBearerToken("bearer-token"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	group, err := client.CreateGroup(context.Background(), sub2api.CreateGroupRequest{
		Name:           "relay-group",
		RateMultiplier: 1.5,
	})
	if err != nil {
		t.Fatalf("CreateGroup() error = %v", err)
	}

	if group.ID != "group_1" {
		t.Fatalf("group.ID = %q, want %q", group.ID, "group_1")
	}

	if group.Name != "relay-group" {
		t.Fatalf("group.Name = %q, want %q", group.Name, "relay-group")
	}
}

func TestSub2APIHostAdapterTestAccountParsesSSE(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	result, err := client.TestAccount(context.Background(), "account_1", "deepseek-chat")
	if err != nil {
		t.Fatalf("TestAccount() error = %v", err)
	}

	if !result.OK {
		t.Fatal("TestAccount() OK = false, want true")
	}

	if result.Status != "passed" {
		t.Fatalf("TestAccount() Status = %q, want %q", result.Status, "passed")
	}

	if result.Message != "smoke passed" {
		t.Fatalf("TestAccount() Message = %q, want %q", result.Message, "smoke passed")
	}
}

func TestSub2APIHostAdapterGetAccountModelsParsesEnvelope(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	models, err := client.GetAccountModels(context.Background(), "account_1")
	if err != nil {
		t.Fatalf("GetAccountModels() error = %v", err)
	}

	if len(models) != 2 {
		t.Fatalf("len(models) = %d, want 2", len(models))
	}

	if models[0].ID != "deepseek-chat" || models[0].DisplayName != "DeepSeek Chat" || models[0].Type != "chat" {
		t.Fatalf("first model = %+v, want id/display_name/type from envelope", models[0])
	}
}

func TestSub2APIHostAdapterChecksGatewayAccess(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL)
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	result, err := client.CheckGatewayAccess(context.Background(), sub2api.GatewayAccessCheckRequest{
		APIKey:        "user-api-key",
		ExpectedModel: "deepseek-chat",
	})
	if err != nil {
		t.Fatalf("CheckGatewayAccess() error = %v", err)
	}
	if !result.OK || !result.HasExpectedModel {
		t.Fatalf("CheckGatewayAccess() = %+v, want ok with expected model", result)
	}
}

func TestSub2APIHostAdapterSeparatesAccountModelsFromGatewayModels(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey:      true,
		version:            "0.1.126",
		accountModels:      []map[string]any{{"id": "deepseek-account-only", "display_name": "DeepSeek Account Only", "type": "chat"}},
		gatewayModels:      []map[string]any{{"id": "deepseek-gateway-only"}},
		gatewayExpectedKey: "managed-user-key",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	accountModels, err := client.GetAccountModels(context.Background(), "account_1")
	if err != nil {
		t.Fatalf("GetAccountModels() error = %v", err)
	}
	if len(accountModels) != 1 || accountModels[0].ID != "deepseek-account-only" {
		t.Fatalf("GetAccountModels() = %+v, want admin account models only", accountModels)
	}

	gatewayResult, err := client.CheckGatewayAccess(context.Background(), sub2api.GatewayAccessCheckRequest{
		APIKey:        "managed-user-key",
		ExpectedModel: "deepseek-gateway-only",
	})
	if err != nil {
		t.Fatalf("CheckGatewayAccess() error = %v", err)
	}
	if !gatewayResult.OK || !gatewayResult.HasExpectedModel {
		t.Fatalf("CheckGatewayAccess() = %+v, want gateway models only", gatewayResult)
	}
	if len(gatewayResult.Models) != 1 || gatewayResult.Models[0] != "deepseek-gateway-only" {
		t.Fatalf("gateway models = %+v, want gateway-only model list", gatewayResult.Models)
	}
	if gatewayResult.Models[0] == accountModels[0].ID {
		t.Fatalf("gateway models = %+v unexpectedly matched account models %+v", gatewayResult.Models, accountModels)
	}
}

func TestSub2APIHostAdapterGatewayProbeDoesNotReuseAdminCredential(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey:      true,
		version:            "0.1.126",
		gatewayExpectedKey: "managed-user-key",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	result, err := client.CheckGatewayAccess(context.Background(), sub2api.GatewayAccessCheckRequest{
		APIKey:        "managed-user-key",
		ExpectedModel: "deepseek-chat",
	})
	if err != nil {
		t.Fatalf("CheckGatewayAccess() error = %v", err)
	}
	if !result.OK {
		t.Fatalf("CheckGatewayAccess() = %+v, want OK=true with managed user key", result)
	}

	wrongKeyResult, err := client.CheckGatewayAccess(context.Background(), sub2api.GatewayAccessCheckRequest{
		APIKey:        "api-key",
		ExpectedModel: "deepseek-chat",
	})
	if err != nil {
		t.Fatalf("CheckGatewayAccess() with admin key error = %v", err)
	}
	if wrongKeyResult.OK {
		t.Fatalf("CheckGatewayAccess() with admin key = %+v, want OK=false", wrongKeyResult)
	}
	if wrongKeyResult.StatusCode != http.StatusUnauthorized {
		t.Fatalf("StatusCode = %d, want %d", wrongKeyResult.StatusCode, http.StatusUnauthorized)
	}
	if wrongKeyResult.HasExpectedModel {
		t.Fatalf("CheckGatewayAccess() with admin key = %+v, want HasExpectedModel=false", wrongKeyResult)
	}
}

func TestSub2APIHostAdapterDeletesManagedResources(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	if err := client.DeleteAccount(context.Background(), "account_1"); err != nil {
		t.Fatalf("DeleteAccount() error = %v", err)
	}
	if err := client.DeleteChannel(context.Background(), "channel_1"); err != nil {
		t.Fatalf("DeleteChannel() error = %v", err)
	}
	if err := client.DeleteGroup(context.Background(), "group_1"); err != nil {
		t.Fatalf("DeleteGroup() error = %v", err)
	}
}

func TestSub2APIHostAdapterListManagedResources(t *testing.T) {
	server := newSub2APIStubServer(t, sub2APIStubConfig{
		requireAPIKey: true,
		version:       "0.1.126",
	})
	defer server.Close()

	client, err := sub2api.NewClient(server.URL, sub2api.WithAPIKey("api-key"))
	if err != nil {
		t.Fatalf("NewClient() error = %v", err)
	}

	snapshot, err := client.ListManagedResources(context.Background(), sub2api.ListManagedResourcesRequest{
		GroupName:         "crm-deepseek-group",
		ChannelName:       "crm-deepseek-channel",
		PlanName:          "crm-deepseek-plan",
		AccountNamePrefix: "deepseek-",
	})
	if err != nil {
		t.Fatalf("ListManagedResources() error = %v", err)
	}

	if len(snapshot.Groups) != 1 || snapshot.Groups[0].ID != "group_1" {
		t.Fatalf("Groups = %+v, want one group_1 match", snapshot.Groups)
	}
	if len(snapshot.Channels) != 2 || snapshot.Channels[0].ID != "channel_1" || snapshot.Channels[1].ID != "channel_2" {
		t.Fatalf("Channels = %+v, want two matching channels", snapshot.Channels)
	}
	if len(snapshot.Plans) != 1 || snapshot.Plans[0].ID != "plan_1" {
		t.Fatalf("Plans = %+v, want one plan_1 match", snapshot.Plans)
	}
	if len(snapshot.Accounts) != 2 || snapshot.Accounts[0].ID != "account_1" || snapshot.Accounts[1].ID != "account_2" {
		t.Fatalf("Accounts = %+v, want two deepseek account matches", snapshot.Accounts)
	}
}

type sub2APIStubConfig struct {
	requireAPIKey      bool
	version            string
	accountModels      []map[string]any
	gatewayModels      []map[string]any
	gatewayExpectedKey string
}

func newSub2APIStubServer(t *testing.T, cfg sub2APIStubConfig) *httptest.Server {
	t.Helper()

	accountModels := cfg.accountModels
	if len(accountModels) == 0 {
		accountModels = []map[string]any{
			{"id": "deepseek-chat", "display_name": "DeepSeek Chat", "type": "chat"},
			{"id": "deepseek-reasoner", "display_name": "DeepSeek Reasoner", "type": "reasoning"},
		}
	}
	gatewayModels := cfg.gatewayModels
	if len(gatewayModels) == 0 {
		gatewayModels = []map[string]any{
			{"id": "deepseek-chat"},
			{"id": "deepseek-reasoner"},
		}
	}
	gatewayExpectedKey := cfg.gatewayExpectedKey
	if gatewayExpectedKey == "" {
		gatewayExpectedKey = "user-api-key"
	}

	mux := http.NewServeMux()
	mux.HandleFunc("/api/v1/admin/system/version", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		if r.Method != http.MethodGet {
			http.NotFound(w, r)
			return
		}
		writeJSON(t, w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"version": cfg.version,
			},
		})
	})
	mux.HandleFunc("/api/v1/admin/groups", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(t, w, http.StatusOK, map[string]any{
				"data": []map[string]any{
					{"id": "group_1", "name": "crm-deepseek-group"},
					{"id": "group_2", "name": "other-group"},
				},
			})
		case http.MethodPost:
			var payload map[string]any
			_ = json.NewDecoder(r.Body).Decode(&payload)
			if payload["name"] == "" || payload["rate_multiplier"] == nil {
				writeJSON(t, w, http.StatusUnprocessableEntity, map[string]any{"error": "validation failed"})
				return
			}
			writeJSON(t, w, http.StatusCreated, map[string]any{
				"data": map[string]any{
					"id":   "group_1",
					"name": payload["name"],
				},
			})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/groups/", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		if r.Method != http.MethodDelete {
			http.NotFound(w, r)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("/api/v1/admin/channels", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(t, w, http.StatusOK, map[string]any{
				"data": []map[string]any{
					{"id": "channel_1", "name": "crm-deepseek-channel"},
					{"id": "channel_2", "name": "crm-deepseek-channel"},
					{"id": "channel_3", "name": "other-channel"},
				},
			})
		case http.MethodPost:
			writeJSON(t, w, http.StatusUnprocessableEntity, map[string]any{"error": "validation failed"})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/channels/", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		if r.Method != http.MethodDelete {
			http.NotFound(w, r)
			return
		}
		w.WriteHeader(http.StatusNoContent)
	})
	mux.HandleFunc("/api/v1/admin/payment/plans", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(t, w, http.StatusOK, map[string]any{
				"data": []map[string]any{
					{"id": "plan_1", "name": "crm-deepseek-plan"},
					{"id": "plan_2", "name": "other-plan"},
				},
			})
		case http.MethodPost:
			writeJSON(t, w, http.StatusUnprocessableEntity, map[string]any{"error": "validation failed"})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/accounts", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		switch r.Method {
		case http.MethodGet:
			writeJSON(t, w, http.StatusOK, map[string]any{
				"data": []map[string]any{
					{"id": "account_1", "name": "deepseek-01"},
					{"id": "account_2", "name": "deepseek-02"},
					{"id": "account_3", "name": "other-01"},
				},
			})
		case http.MethodPost:
			writeJSON(t, w, http.StatusUnprocessableEntity, map[string]any{"error": "validation failed"})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/accounts/batch", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		if r.Method != http.MethodPost {
			http.NotFound(w, r)
			return
		}
		writeJSON(t, w, http.StatusOK, map[string]any{
			"data": []map[string]any{
				{"id": "account_1"},
				{"id": "account_2"},
			},
		})
	})
	mux.HandleFunc("/api/v1/admin/accounts/", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		parts := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/v1/admin/accounts/"), "/")
		if len(parts) == 1 {
			if r.Method != http.MethodDelete {
				http.NotFound(w, r)
				return
			}
			w.WriteHeader(http.StatusNoContent)
			return
		}
		if len(parts) != 2 {
			http.NotFound(w, r)
			return
		}
		accountID, action := parts[0], parts[1]
		switch action {
		case "test":
			if r.Method == http.MethodGet {
				writeJSON(t, w, http.StatusOK, map[string]any{"data": map[string]any{"supported": true}})
				return
			}
			if r.Method != http.MethodPost {
				http.NotFound(w, r)
				return
			}
			w.Header().Set("Content-Type", "text/event-stream")
			w.WriteHeader(http.StatusOK)
			fmt.Fprintf(w, "event: result\n")
			fmt.Fprintf(w, "data: {\"status\":\"passed\",\"message\":\"smoke passed\",\"ok\":true,\"account_id\":\"%s\"}\n\n", accountID)
		case "models":
			if r.Method != http.MethodGet {
				http.NotFound(w, r)
				return
			}
			writeJSON(t, w, http.StatusOK, map[string]any{
				"data": map[string]any{
					"items": accountModels,
				},
			})
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/api/v1/admin/subscriptions/assign", func(w http.ResponseWriter, r *http.Request) {
		if !mustStubAuth(t, w, r, cfg.requireAPIKey) {
			return
		}
		if r.Method == http.MethodGet {
			writeJSON(t, w, http.StatusOK, map[string]any{
				"data": map[string]any{"supported": true},
			})
			return
		}
		if r.Method != http.MethodPost {
			http.NotFound(w, r)
			return
		}
		writeJSON(t, w, http.StatusOK, map[string]any{
			"data": map[string]any{
				"id": "subscription_1",
			},
		})
	})
	mux.HandleFunc("/v1/models", func(w http.ResponseWriter, r *http.Request) {
		if got := r.Header.Get("Authorization"); got != "Bearer "+gatewayExpectedKey {
			w.WriteHeader(http.StatusUnauthorized)
			_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
			return
		}
		writeJSON(t, w, http.StatusOK, map[string]any{
			"data": gatewayModels,
		})
	})
	mux.HandleFunc("/v1/chat/completions", func(w http.ResponseWriter, r *http.Request) {
		if got := r.Header.Get("Authorization"); got != "Bearer "+gatewayExpectedKey {
			w.WriteHeader(http.StatusUnauthorized)
			_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
			return
		}
		writeJSON(t, w, http.StatusOK, map[string]any{
			"id":     "chatcmpl_stub",
			"object": "chat.completion",
			"choices": []map[string]any{
				{
					"index": 0,
					"message": map[string]any{
						"role":    "assistant",
						"content": "pong",
					},
				},
			},
		})
	})

	return httptest.NewServer(mux)
}

func mustStubAuth(t *testing.T, w http.ResponseWriter, r *http.Request, requireAPIKey bool) bool {
	t.Helper()

	if !requireAPIKey {
		return true
	}

	if got := r.Header.Get("x-api-key"); got == "api-key" {
		if r.Header.Get("Authorization") != "" {
			t.Fatalf("Authorization header = %q, want empty when x-api-key is configured", r.Header.Get("Authorization"))
		}
		return true
	}

	if got := r.Header.Get("Authorization"); got == "Bearer bearer-token" {
		return true
	}

	w.WriteHeader(http.StatusUnauthorized)
	_, _ = w.Write([]byte(`{"error":"unauthorized"}`))
	return false
}

func writeJSON(t *testing.T, w http.ResponseWriter, statusCode int, payload any) {
	t.Helper()

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(statusCode)
	if err := json.NewEncoder(w).Encode(payload); err != nil {
		t.Fatalf("json.Encode() error = %v", err)
	}
}
