package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/domain"
)

// mockCredentialStore implements port.CredentialStore for testing.
type mockCredentialStore struct {
	creds map[string]domain.Credential
	err   error
}

func newMockCredentialStore() *mockCredentialStore {
	return &mockCredentialStore{
		creds: make(map[string]domain.Credential),
	}
}

func (m *mockCredentialStore) Get(_ context.Context, key string) (string, error) {
	if m.err != nil {
		return "", m.err
	}
	c, ok := m.creds[key]
	if !ok {
		return "", nil
	}
	return c.Value, nil
}

func (m *mockCredentialStore) GetRequired(_ context.Context, key string) (string, error) {
	if m.err != nil {
		return "", m.err
	}
	c, ok := m.creds[key]
	if !ok {
		return "", fmt.Errorf("credential not found: %s", key)
	}
	return c.Value, nil
}

func (m *mockCredentialStore) Set(_ context.Context, cred domain.Credential) error {
	if m.err != nil {
		return m.err
	}
	cred.CreatedAt = time.Now()
	cred.UpdatedAt = time.Now()
	m.creds[cred.Key] = cred
	return nil
}

func (m *mockCredentialStore) Delete(_ context.Context, key string) error {
	if m.err != nil {
		return m.err
	}
	if _, ok := m.creds[key]; !ok {
		return domain.ErrCredentialNotFound
	}
	delete(m.creds, key)
	return nil
}

func (m *mockCredentialStore) List(_ context.Context) ([]domain.Credential, error) {
	if m.err != nil {
		return nil, m.err
	}
	var result []domain.Credential
	for _, c := range m.creds {
		result = append(result, c)
	}
	return result, nil
}

func (m *mockCredentialStore) ListByCategory(_ context.Context, category string) ([]domain.Credential, error) {
	if m.err != nil {
		return nil, m.err
	}
	var result []domain.Credential
	for _, c := range m.creds {
		if c.Category == category {
			result = append(result, c)
		}
	}
	return result, nil
}

func (m *mockCredentialStore) GetMultiple(_ context.Context, keys []string) (map[string]string, error) {
	if m.err != nil {
		return nil, m.err
	}
	result := make(map[string]string)
	for _, k := range keys {
		if c, ok := m.creds[k]; ok {
			result[k] = c.Value
		}
	}
	return result, nil
}

func (m *mockCredentialStore) SetMultiple(_ context.Context, creds []domain.Credential) error {
	if m.err != nil {
		return m.err
	}
	for _, c := range creds {
		c.CreatedAt = time.Now()
		c.UpdatedAt = time.Now()
		m.creds[c.Key] = c
	}
	return nil
}

func setupCredentialsHandler() (*CredentialsHandler, *mockCredentialStore, chi.Router) {
	store := newMockCredentialStore()
	h := NewCredentialsHandler(store)
	r := chi.NewRouter()
	r.Use(testAdminAuth) // Add auth middleware for tests
	h.Mount(r)
	return h, store, r
}

func TestCredentialsHandler_List(t *testing.T) {
	t.Run("empty list", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()
		req := httptest.NewRequest("GET", "/credentials", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
		}

		var resp map[string]any
		if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
			t.Fatalf("failed to decode response: %v", err)
		}
		data, ok := resp["data"].([]any)
		if !ok {
			t.Fatalf("response missing data array")
		}
		if len(data) != 0 {
			t.Errorf("data length = %d, want 0", len(data))
		}
	})

	t.Run("with credentials", func(t *testing.T) {
		_, store, router := setupCredentialsHandler()
		store.creds["MY_TOKEN"] = domain.Credential{
			Key:       "MY_TOKEN",
			Value:     "****",
			Category:  "gitea",
			CreatedAt: time.Now(),
			UpdatedAt: time.Now(),
		}

		req := httptest.NewRequest("GET", "/credentials", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
		}

		var resp map[string]any
		if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
			t.Fatalf("failed to decode response: %v", err)
		}
		data, ok := resp["data"].([]any)
		if !ok {
			t.Fatalf("response missing data array")
		}
		if len(data) != 1 {
			t.Errorf("data length = %d, want 1", len(data))
		}
	})

	t.Run("filter by category", func(t *testing.T) {
		_, store, router := setupCredentialsHandler()
		store.creds["GITEA_TOKEN"] = domain.Credential{
			Key: "GITEA_TOKEN", Value: "****", Category: "gitea",
			CreatedAt: time.Now(), UpdatedAt: time.Now(),
		}
		store.creds["CF_TOKEN"] = domain.Credential{
			Key: "CF_TOKEN", Value: "****", Category: "cloudflare",
			CreatedAt: time.Now(), UpdatedAt: time.Now(),
		}

		req := httptest.NewRequest("GET", "/credentials?category=gitea", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
		}

		var resp map[string]any
		if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
			t.Fatalf("failed to decode response: %v", err)
		}
		data := resp["data"].([]any)
		if len(data) != 1 {
			t.Errorf("data length = %d, want 1", len(data))
		}
	})
}

func TestCredentialsHandler_Get(t *testing.T) {
	t.Run("existing credential", func(t *testing.T) {
		_, store, router := setupCredentialsHandler()
		store.creds["MY_TOKEN"] = domain.Credential{
			Key: "MY_TOKEN", Value: "secret123",
			CreatedAt: time.Now(), UpdatedAt: time.Now(),
		}

		req := httptest.NewRequest("GET", "/credentials/MY_TOKEN", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
		}

		var resp map[string]any
		if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
			t.Fatalf("failed to decode response: %v", err)
		}
		data := resp["data"].(map[string]any)
		if data["key"] != "MY_TOKEN" {
			t.Errorf("key = %q, want %q", data["key"], "MY_TOKEN")
		}
		if data["value"] != "secret123" {
			t.Errorf("value = %q, want %q", data["value"], "secret123")
		}
	})

	t.Run("not found", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		req := httptest.NewRequest("GET", "/credentials/MISSING", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusNotFound {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
		}
	})
}

func TestCredentialsHandler_Set(t *testing.T) {
	t.Run("valid credential", func(t *testing.T) {
		_, store, router := setupCredentialsHandler()

		body, _ := json.Marshal(SetCredentialRequest{
			Key:         "NEW_TOKEN",
			Value:       "secret",
			Description: "A test token",
			Category:    "gitea",
		})
		req := httptest.NewRequest("POST", "/credentials", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusCreated {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusCreated)
		}

		if _, ok := store.creds["NEW_TOKEN"]; !ok {
			t.Error("credential not stored")
		}
	})

	t.Run("missing key", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		body, _ := json.Marshal(SetCredentialRequest{Value: "secret"})
		req := httptest.NewRequest("POST", "/credentials", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("missing value", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		body, _ := json.Marshal(SetCredentialRequest{Key: "TOKEN"})
		req := httptest.NewRequest("POST", "/credentials", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("invalid json", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		req := httptest.NewRequest("POST", "/credentials", bytes.NewReader([]byte("not json")))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})
}

func TestCredentialsHandler_SetBatch(t *testing.T) {
	t.Run("valid batch", func(t *testing.T) {
		_, store, router := setupCredentialsHandler()

		body, _ := json.Marshal(SetBatchRequest{
			Credentials: []SetCredentialRequest{
				{Key: "TOKEN1", Value: "val1"},
				{Key: "TOKEN2", Value: "val2"},
			},
		})
		req := httptest.NewRequest("POST", "/credentials/batch", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusCreated {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusCreated)
		}

		if len(store.creds) != 2 {
			t.Errorf("stored credentials = %d, want 2", len(store.creds))
		}
	})

	t.Run("empty array", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		body, _ := json.Marshal(SetBatchRequest{Credentials: []SetCredentialRequest{}})
		req := httptest.NewRequest("POST", "/credentials/batch", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("missing key in batch", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		body, _ := json.Marshal(SetBatchRequest{
			Credentials: []SetCredentialRequest{
				{Key: "TOKEN1", Value: "val1"},
				{Key: "", Value: "val2"},
			},
		})
		req := httptest.NewRequest("POST", "/credentials/batch", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("missing value in batch", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		body, _ := json.Marshal(SetBatchRequest{
			Credentials: []SetCredentialRequest{
				{Key: "TOKEN1", Value: ""},
			},
		})
		req := httptest.NewRequest("POST", "/credentials/batch", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})
}

func TestCredentialsHandler_Delete(t *testing.T) {
	t.Run("existing credential", func(t *testing.T) {
		_, store, router := setupCredentialsHandler()
		store.creds["TO_DELETE"] = domain.Credential{
			Key: "TO_DELETE", Value: "val",
			CreatedAt: time.Now(), UpdatedAt: time.Now(),
		}

		req := httptest.NewRequest("DELETE", "/credentials/TO_DELETE", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
		}

		var resp map[string]any
		if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil {
			t.Fatalf("failed to decode response: %v", err)
		}
		data := resp["data"].(map[string]any)
		if data["status"] != "deleted" {
			t.Errorf("status = %q, want %q", data["status"], "deleted")
		}
	})

	t.Run("not found returns 404", func(t *testing.T) {
		_, _, router := setupCredentialsHandler()

		req := httptest.NewRequest("DELETE", "/credentials/NONEXISTENT", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusNotFound {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
		}
	})

	t.Run("store error returns 500", func(t *testing.T) {
		_, store, router := setupCredentialsHandler()
		store.err = fmt.Errorf("database connection lost")

		req := httptest.NewRequest("DELETE", "/credentials/ANY_KEY", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})
}
