package plane

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

	"github.com/nomadcode/nomadcode-core/internal/workitem"
)

func TestGetWorkItemUsesWorkItemsEndpointAndAPIKey(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Fatalf("method: got %s", r.Method)
		}
		if r.URL.Path != "/api/v1/workspaces/general/projects/project-1/work-items/work-1" {
			t.Fatalf("path: got %s", r.URL.Path)
		}
		if r.Header.Get("X-Api-Key") != "test-token" {
			t.Fatalf("missing API key header: %#v", r.Header)
		}
		_ = json.NewEncoder(w).Encode(map[string]any{
			"id":                   "work-1",
			"name":                 "Fix issue",
			"description_stripped": "Do the thing",
		})
	}))
	defer server.Close()

	client := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
	item, err := client.GetWorkItem(context.Background(), WorkItemRef{
		WorkspaceSlug: "general",
		ProjectID:     "project-1",
		WorkItemID:    "work-1",
	})
	if err != nil {
		t.Fatalf("GetWorkItem returned error: %v", err)
	}
	if item.ID != "work-1" || item.Name != "Fix issue" || item.DescriptionStripped != "Do the thing" {
		t.Fatalf("unexpected item: %#v", item)
	}
}

func TestAddCommentPostsHTMLAndExternalFields(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Fatalf("method: got %s", r.Method)
		}
		if r.URL.Path != "/api/v1/workspaces/general/projects/project-1/work-items/work-1/comments/" {
			t.Fatalf("path: got %s", r.URL.Path)
		}
		if r.Header.Get("Content-Type") != "application/json" {
			t.Fatalf("content type: got %s", r.Header.Get("Content-Type"))
		}
		var body map[string]string
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			t.Fatalf("decode body: %v", err)
		}
		if body["comment_html"] != "<p>done</p>" || body["external_source"] != "nomadcode" || body["external_id"] != "task-1" {
			t.Fatalf("unexpected body: %#v", body)
		}
		w.WriteHeader(http.StatusCreated)
	}))
	defer server.Close()

	client := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
	err := client.AddComment(context.Background(), AddCommentInput{
		Ref: WorkItemRef{
			WorkspaceSlug: "general",
			ProjectID:     "project-1",
			WorkItemID:    "work-1",
		},
		CommentHTML:    "<p>done</p>",
		ExternalSource: "nomadcode",
		ExternalID:     "task-1",
	})
	if err != nil {
		t.Fatalf("AddComment returned error: %v", err)
	}
}

func TestUpdateIssueStatusPatchesState(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPatch {
			t.Fatalf("method: got %s", r.Method)
		}
		if r.URL.Path != "/api/v1/workspaces/general/projects/project-1/work-items/work-1/" {
			t.Fatalf("path: got %s", r.URL.Path)
		}
		var body map[string]string
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			t.Fatalf("decode body: %v", err)
		}
		if body["state"] != "state-1" {
			t.Fatalf("unexpected body: %#v", body)
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
	err := client.UpdateIssueStatus(context.Background(), UpdateIssueStatusInput{
		Ref: WorkItemRef{
			WorkspaceSlug: "general",
			ProjectID:     "project-1",
			WorkItemID:    "work-1",
		},
		Status: "state-1",
	})
	if err != nil {
		t.Fatalf("UpdateIssueStatus returned error: %v", err)
	}
}

func TestClientReturnsResponseBodyOnError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "not allowed", http.StatusForbidden)
	}))
	defer server.Close()

	client := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
	_, err := client.GetWorkItem(context.Background(), WorkItemRef{
		WorkspaceSlug: "general",
		ProjectID:     "project-1",
		WorkItemID:    "work-1",
	})
	if err == nil || !strings.Contains(err.Error(), "status 403") || !strings.Contains(err.Error(), "not allowed") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestClientImplementsWorkItemFacets(t *testing.T) {
	client := NewClient(Config{BaseURL: "https://example.com", Token: "test-token"}, nil)
	if client.ProviderID() != workitem.ProviderID("plane") {
		t.Errorf("provider ID: got %q", client.ProviderID())
	}
	caps := client.Capabilities()
	if !caps.Supports(workitem.CapabilityLookup) {
		t.Error("expected lookup capability")
	}
	if !caps.Supports(workitem.CapabilityComment) {
		t.Error("expected comment capability")
	}
	if !caps.Supports(workitem.CapabilityStatusProjection) {
		t.Error("expected status projection capability")
	}
	if caps.Supports(workitem.CapabilityLabelProjection) {
		t.Error("label projection should be unsupported")
	}
}

func TestFetchWorkItemMapsNeutralRefToPlaneEndpoint(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Fatalf("method: got %s", r.Method)
		}
		if r.URL.Path != "/api/v1/workspaces/general/projects/project-1/work-items/work-1" {
			t.Fatalf("path: got %s", r.URL.Path)
		}
		_ = json.NewEncoder(w).Encode(map[string]any{
			"id":                   "work-1",
			"name":                 "Fix issue",
			"description_stripped": "Do the thing",
			"description_html":     "<p>Do the thing</p>",
		})
	}))
	defer server.Close()

	client := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
	ref := workitem.Ref{Provider: "plane", Tenant: "general", Project: "project-1", ID: "work-1"}
	item, err := client.FetchWorkItem(context.Background(), ref)
	if err != nil {
		t.Fatalf("FetchWorkItem returned error: %v", err)
	}
	if item.Title != "Fix issue" {
		t.Errorf("title: got %q", item.Title)
	}
	if item.DescriptionText != "Do the thing" {
		t.Errorf("description_text: got %q", item.DescriptionText)
	}
	if item.DescriptionHTML != "<p>Do the thing</p>" {
		t.Errorf("description_html: got %q", item.DescriptionHTML)
	}
	if item.Ref.ID != "work-1" {
		t.Errorf("ref.ID: got %q", item.Ref.ID)
	}
}

func TestAppendCommentMapsNeutralComment(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Fatalf("method: got %s", r.Method)
		}
		if r.URL.Path != "/api/v1/workspaces/general/projects/project-1/work-items/work-1/comments/" {
			t.Fatalf("path: got %s", r.URL.Path)
		}
		var body map[string]string
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			t.Fatalf("decode body: %v", err)
		}
		if body["comment_html"] != "<p>done</p>" {
			t.Errorf("comment_html: got %q", body["comment_html"])
		}
		if body["external_source"] != "nomadcode" {
			t.Errorf("external_source: got %q", body["external_source"])
		}
		if body["external_id"] != "task-1" {
			t.Errorf("external_id: got %q", body["external_id"])
		}
		if body["access"] != "INTERNAL" {
			t.Errorf("access: got %q", body["access"])
		}
		w.WriteHeader(http.StatusCreated)
	}))
	defer server.Close()

	client := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
	err := client.AppendComment(context.Background(), workitem.CommentInput{
		Ref:            workitem.Ref{Provider: "plane", Tenant: "general", Project: "project-1", ID: "work-1"},
		Body:           "<p>done</p>",
		Format:         workitem.CommentFormatHTML,
		ExternalSource: "nomadcode",
		ExternalID:     "task-1",
		Visibility:     "INTERNAL",
	})
	if err != nil {
		t.Fatalf("AppendComment returned error: %v", err)
	}
}

func TestProjectStatusRequiresProviderState(t *testing.T) {
	client := NewClient(Config{BaseURL: "https://example.com", Token: "test-token"}, nil)
	err := client.ProjectStatus(context.Background(), workitem.StatusProjection{
		Ref:           workitem.Ref{Provider: "plane", Tenant: "general", Project: "project-1", ID: "work-1"},
		ProviderState: "",
	})
	if err == nil || !strings.Contains(err.Error(), ErrInvalidInput.Error()) {
		t.Fatalf("empty provider state: expected ErrInvalidInput, got %v", err)
	}

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPatch {
			t.Fatalf("method: got %s", r.Method)
		}
		if r.URL.Path != "/api/v1/workspaces/general/projects/project-1/work-items/work-1/" {
			t.Fatalf("path: got %s", r.URL.Path)
		}
		var body map[string]string
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			t.Fatalf("decode body: %v", err)
		}
		if body["state"] != "state-done" {
			t.Errorf("state: got %q", body["state"])
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client2 := NewClient(Config{BaseURL: server.URL, Token: "test-token"}, nil)
	err = client2.ProjectStatus(context.Background(), workitem.StatusProjection{
		Ref:            workitem.Ref{Provider: "plane", Tenant: "general", Project: "project-1", ID: "work-1"},
		CanonicalState: "done",
		ProviderState:  "state-done",
	})
	if err != nil {
		t.Fatalf("ProjectStatus returned error: %v", err)
	}
}

func TestClientRequiresBaseURLAndToken(t *testing.T) {
	client := NewClient(Config{}, nil)
	_, err := client.GetWorkItem(context.Background(), WorkItemRef{
		WorkspaceSlug: "general",
		ProjectID:     "project-1",
		WorkItemID:    "work-1",
	})
	if err == nil || !strings.Contains(err.Error(), ErrMissingConfig.Error()) {
		t.Fatalf("expected missing config error, got %v", err)
	}
}
