package gitcode

import (
	"context"
	"errors"
	"net/http"
	"strings"
	"testing"
)

func TestFixtureProviderContract(t *testing.T) {
	provider, err := NewFixtureProvider(FixtureConfig{Pagination: PaginationConfig{PerPage: 1}})
	if err != nil {
		t.Fatalf("NewFixtureProvider: %v", err)
	}
	ctx := context.Background()
	auth, err := provider.ProbeAuth(ctx, AuthProbeRequest{Owner: "example-owner", Repo: "example-repo"})
	if err != nil || !auth.Authenticated || auth.TokenPresent {
		t.Fatalf("unexpected auth probe auth=%+v err=%v", auth, err)
	}
	repo, err := provider.GetRepo(ctx, RepoRequest{Owner: "example-owner", Repo: "example-repo"})
	if err != nil || repo.FullName != "example-owner/example-repo" {
		t.Fatalf("unexpected repo repo=%+v err=%v", repo, err)
	}
	issues, err := provider.ListIssues(ctx, IssueListRequest{Owner: "example-owner", Repo: "example-repo"})
	if err != nil || len(issues.Items) != 2 || issues.Items[1].ID != "ISSUE-42" {
		t.Fatalf("unexpected issues page=%+v err=%v", issues, err)
	}
	issue, err := provider.GetIssue(ctx, IssueRequest{Owner: "example-owner", Repo: "example-repo", Number: 42})
	if err != nil || issue.ID != "ISSUE-42" || !strings.Contains(issue.Body, "Structured body") {
		t.Fatalf("unexpected issue issue=%+v err=%v", issue, err)
	}
	comments, err := provider.ListIssueComments(ctx, IssueRequest{Owner: "example-owner", Repo: "example-repo", Number: 42})
	if err != nil || len(comments.Items) != 1 || comments.Items[0].ID != "COMMENT-1" {
		t.Fatalf("unexpected comments page=%+v err=%v", comments, err)
	}
	wikis, err := provider.ListWikiPages(ctx, WikiListRequest{Owner: "example-owner", Repo: "example-repo"})
	if err != nil || len(wikis.Items) != 2 || wikis.Items[0].Slug != "Home" {
		t.Fatalf("unexpected wikis page=%+v err=%v", wikis, err)
	}
	wiki, err := provider.GetWikiPage(ctx, WikiPageRequest{Owner: "example-owner", Repo: "example-repo", Slug: "Home"})
	if err != nil || wiki.ID != "WIKI-HOME" || !strings.Contains(wiki.Body, "api.example.com") {
		t.Fatalf("unexpected wiki page=%+v err=%v", wiki, err)
	}
	search, err := provider.Search(ctx, SearchRequest{Query: "cache", Owner: "example-owner", Repo: "example-repo"})
	if err != nil || len(search.Items) == 0 {
		t.Fatalf("unexpected search page=%+v err=%v", search, err)
	}
}

func TestFixtureProviderScenarios(t *testing.T) {
	tests := []struct {
		scenario string
		check    func(error) bool
	}{
		{"auth-error", func(err error) bool { var target ErrAuthExpired; return errors.As(err, &target) }},
		{"conflict", func(err error) bool { var target ErrConflict; return errors.As(err, &target) }},
		{"rate-limit", func(err error) bool {
			var target ErrRateLimited
			return errors.As(err, &target) && target.RawRetryAfter == "1"
		}},
	}
	for _, tt := range tests {
		t.Run(tt.scenario, func(t *testing.T) {
			provider, err := NewFixtureProvider(FixtureConfig{Scenario: tt.scenario})
			if err != nil {
				t.Fatalf("NewFixtureProvider: %v", err)
			}
			_, err = provider.ListIssues(context.Background(), IssueListRequest{Owner: "example-owner", Repo: "example-repo"})
			if !tt.check(err) {
				t.Fatalf("unexpected error %T %v", err, err)
			}
		})
	}
}

func TestScenario004LiveProviderAdmission(t *testing.T) {
	tests := []struct {
		name string
		cfg  ProviderConfig
	}{
		{name: "non-live-mode", cfg: ProviderConfig{Mode: ProviderModeFixture, LiveAllowed: true, Token: "token", BaseURL: "https://selected.example.test"}},
		{name: "missing-live-allowed", cfg: ProviderConfig{Mode: ProviderModeLive, Token: "token", BaseURL: "https://selected.example.test"}},
		{name: "missing-token", cfg: ProviderConfig{Mode: ProviderModeLive, LiveAllowed: true, BaseURL: "https://selected.example.test"}},
		{name: "empty-base-url", cfg: ProviderConfig{Mode: ProviderModeLive, LiveAllowed: true, Token: "token"}},
		{name: "relative-base-url", cfg: ProviderConfig{Mode: ProviderModeLive, LiveAllowed: true, Token: "token", BaseURL: "/api/v5"}},
		{name: "malformed-base-url", cfg: ProviderConfig{Mode: ProviderModeLive, LiveAllowed: true, Token: "token", BaseURL: "http://[::1"}},
		{name: "unsupported-scheme", cfg: ProviderConfig{Mode: ProviderModeLive, LiveAllowed: true, Token: "token", BaseURL: "file:///tmp/gitcode"}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			called := false
			old := newHTTPClientForProvider
			newHTTPClientForProvider = func(Config) (*HTTPClient, error) {
				called = true
				return &HTTPClient{}, nil
			}
			defer func() { newHTTPClientForProvider = old }()
			if _, err := NewLiveProvider(tt.cfg); !IsProviderUnavailable(err) {
				t.Fatalf("expected unavailable, got %T %v", err, err)
			}
			if called {
				t.Fatalf("HTTP client constructed for invalid live provider")
			}
		})
	}

	called := false
	old := newHTTPClientForProvider
	newHTTPClientForProvider = func(cfg Config) (*HTTPClient, error) {
		called = true
		if cfg.BaseURL != "https://selected.example.test" || cfg.Token != "token" {
			t.Fatalf("unexpected provider config: %+v", cfg)
		}
		return &HTTPClient{}, nil
	}
	defer func() { newHTTPClientForProvider = old }()
	if _, err := NewLiveProvider(ProviderConfig{Mode: ProviderModeLive, LiveAllowed: true, Token: "token", BaseURL: "https://selected.example.test"}); err != nil {
		t.Fatalf("expected admitted live provider: %v", err)
	}
	if !called {
		t.Fatalf("HTTP client not constructed for admitted live provider")
	}
}

func TestProviderWriteUnavailableDoesNotConfirm(t *testing.T) {
	providers := map[string]Provider{
		"unavailable": NewUnavailableProvider("write disabled"),
	}
	for name, provider := range providers {
		t.Run(name, func(t *testing.T) {
			result, err := provider.CreateIssue(context.Background(), CreateIssueRequest{Owner: "example-owner", Repo: "example-repo", Title: "blocked"}, WriteOptions{IdempotencyKey: "key"})
			if !IsProviderUnavailable(err) {
				t.Fatalf("expected provider unavailable, got %T %v", err, err)
			}
			if result.Confirmed || result.IdempotencyKey != "" || result.ResponseHash != "" || !result.ConfirmedAt.IsZero() {
				t.Fatalf("unavailable provider returned success-shaped metadata: %+v", result)
			}
		})
	}
}

func TestScenario005FixtureBoundaryContract(t *testing.T) {
	provider := mustFixtureProvider(t)
	if !IsFixtureBoundary(provider) {
		t.Fatalf("fixture provider does not expose fixture boundary")
	}
	boundary := provider.(FixtureBoundary)
	markers := boundary.FixtureMarkerIDs()
	if len(markers) != 2 || markers[0] != FixtureIssueMarker || markers[1] != FixtureWikiMarker {
		t.Fatalf("fixture markers = %#v", markers)
	}
}

func TestScenario005FixtureReadOnlyTyped(t *testing.T) {
	provider := mustFixtureProvider(t)
	ctx := context.Background()
	tests := []struct {
		name string
		run  func() error
	}{
		{name: "create-issue", run: func() error { _, err := provider.CreateIssue(ctx, CreateIssueRequest{}, WriteOptions{}); return err }},
		{name: "update-issue", run: func() error { _, err := provider.UpdateIssue(ctx, UpdateIssueRequest{}, WriteOptions{}); return err }},
		{name: "create-comment", run: func() error {
			_, err := provider.CreateIssueComment(ctx, CreateIssueCommentRequest{}, WriteOptions{})
			return err
		}},
		{name: "create-pr", run: func() error { _, err := provider.CreatePR(ctx, CreatePRRequest{}, WriteOptions{}); return err }},
		{name: "update-pr", run: func() error { _, err := provider.UpdatePR(ctx, UpdatePRRequest{}, WriteOptions{}); return err }},
		{name: "merge-pr", run: func() error { _, err := provider.MergePR(ctx, MergePRRequest{}, WriteOptions{}); return err }},
		{name: "link-pr-issue", run: func() error { _, err := provider.LinkPRIssue(ctx, LinkPRIssueRequest{}, WriteOptions{}); return err }},
		{name: "create-pr-comment", run: func() error {
			_, err := provider.CreatePRComment(ctx, CreatePRCommentRequest{}, WriteOptions{})
			return err
		}},
		{name: "create-wiki", run: func() error {
			_, err := provider.CreateWikiPage(ctx, CreateWikiPageRequest{}, WriteOptions{})
			return err
		}},
		{name: "update-wiki", run: func() error {
			_, err := provider.UpdateWikiPage(ctx, UpdateWikiPageRequest{}, WriteOptions{})
			return err
		}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := tt.run()
			if !IsFixtureReadOnly(err) {
				t.Fatalf("expected fixture read-only classification, got %T %v", err, err)
			}
			if IsProviderUnavailable(err) {
				t.Fatalf("fixture read-only should not be provider unavailable")
			}
			if !strings.Contains(err.Error(), "fixture client is read-only") {
				t.Fatalf("read-only message changed: %v", err)
			}
		})
	}
}

func mustFixtureProvider(t *testing.T) Provider {
	t.Helper()
	provider, err := NewFixtureProvider(FixtureConfig{})
	if err != nil {
		t.Fatalf("NewFixtureProvider: %v", err)
	}
	return provider
}

func TestProviderPaginationGuards(t *testing.T) {
	t.Run("malformed fixture", func(t *testing.T) {
		provider, err := NewFixtureProvider(FixtureConfig{Scenario: "malformed-page"})
		if err != nil {
			t.Fatalf("NewFixtureProvider: %v", err)
		}
		_, err = provider.ListIssues(context.Background(), IssueListRequest{Owner: "example-owner", Repo: "example-repo"})
		var target ErrPaginationMalformed
		if !errors.As(err, &target) {
			t.Fatalf("expected ErrPaginationMalformed, got %T %v", err, err)
		}
	})
	t.Run("loop fixture", func(t *testing.T) {
		provider, err := NewFixtureProvider(FixtureConfig{Scenario: "pagination-loop"})
		if err != nil {
			t.Fatalf("NewFixtureProvider: %v", err)
		}
		_, err = provider.ListIssues(context.Background(), IssueListRequest{Owner: "example-owner", Repo: "example-repo"})
		var target ErrPaginationLoop
		if !errors.As(err, &target) {
			t.Fatalf("expected ErrPaginationLoop, got %T %v", err, err)
		}
	})
}

func TestRedactedCapture(t *testing.T) {
	capture := NewRedactedCapture("https://secret.gitcode.invalid/api?access_token=raw-token", http.Header{"Authorization": []string{"Bearer raw-token"}, "Cookie": []string{"sid=raw-cookie"}, "X-Trace": []string{"owner-a/repo-a"}}, []byte(`{\"token\":\"raw-token\",\"owner\":\"owner-a\",\"repo\":\"repo-a\",\"body\":\"Bearer raw-token at secret.gitcode.invalid\"}`), errors.New("Bearer raw-token failed for owner-a"), []string{"api.example.com"}, "raw-token", "raw-cookie", "owner-a", "repo-a", "secret.gitcode.invalid")
	if !CaptureIsSanitized(capture, "raw-token", "raw-cookie", "owner-a", "repo-a", "secret.gitcode.invalid") {
		t.Fatalf("capture not sanitized: %+v body=%s", capture, string(capture.Body))
	}
	if !strings.Contains(capture.URL, "redacted.example.com") {
		t.Fatalf("host not redacted: %s", capture.URL)
	}
}

func TestRequireLiveProviderForTestGate(t *testing.T) {
	t.Setenv("GITCODE_LIVE_TEST", "")
	t.Setenv("GITCODE_LIVE_TOKEN", "token")
	if _, ok := RequireLiveProviderForTest(); ok {
		t.Fatalf("live provider gate admitted without GITCODE_LIVE_TEST=1")
	}
	t.Setenv("GITCODE_LIVE_TEST", "1")
	t.Setenv("GITCODE_LIVE_TOKEN", "")
	t.Setenv("GITCODE_TEST_TOKEN", "")
	if _, ok := RequireLiveProviderForTest(); ok {
		t.Fatalf("live provider gate admitted without token")
	}
	t.Setenv("GITCODE_TEST_TOKEN", "alias-token")
	if token, ok := RequireLiveProviderForTest(); !ok || token != "alias-token" {
		t.Fatalf("live provider gate did not accept transitional alias")
	}
}