package gitcode

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"os"
	"strings"
	"time"
)

type Provider interface {
	ProbeAuth(context.Context, AuthProbeRequest) (AuthProbeResult, error)
	GetRepo(context.Context, RepoRequest) (Repo, error)
	ListIssues(context.Context, IssueListRequest) (Page[IssueSummary], error)
	GetIssue(context.Context, IssueRequest) (Issue, error)
	ListIssueComments(context.Context, IssueRequest) (Page[Comment], error)
	ListPRs(context.Context, PRListRequest) (Page[PullRequest], error)
	GetPR(context.Context, PRRequest) (PullRequest, error)
	ListPRComments(context.Context, PRRequest) (Page[PRComment], error)
	CreatePR(context.Context, CreatePRRequest, WriteOptions) (WriteResult[PullRequest], error)
	UpdatePR(context.Context, UpdatePRRequest, WriteOptions) (WriteResult[PullRequest], error)
	MergePR(context.Context, MergePRRequest, WriteOptions) (WriteResult[PullRequest], error)
	LinkPRIssue(context.Context, LinkPRIssueRequest, WriteOptions) (WriteResult[[]Issue], error)
	ListWikiPages(context.Context, WikiListRequest) (Page[WikiPage], error)
	GetWikiPage(context.Context, WikiPageRequest) (WikiPage, error)
	Search(context.Context, SearchRequest) (Page[SearchResult], error)
	CreateIssue(context.Context, CreateIssueRequest, WriteOptions) (WriteResult[Issue], error)
	UpdateIssue(context.Context, UpdateIssueRequest, WriteOptions) (WriteResult[Issue], error)
	CreateIssueComment(context.Context, CreateIssueCommentRequest, WriteOptions) (WriteResult[Comment], error)
	UpdateIssueComment(context.Context, UpdateIssueCommentRequest, WriteOptions) (WriteResult[Comment], error)
	CreatePRComment(context.Context, CreatePRCommentRequest, WriteOptions) (WriteResult[PRComment], error)
	CreatePRReviewComment(context.Context, CreatePRReviewCommentRequest, WriteOptions) (WriteResult[PRComment], error)
	ReplyPRReviewComment(context.Context, ReplyPRReviewCommentRequest, WriteOptions) (WriteResult[PRComment], error)
	CreateWikiPage(context.Context, CreateWikiPageRequest, WriteOptions) (WriteResult[WikiPage], error)
	UpdateWikiPage(context.Context, UpdateWikiPageRequest, WriteOptions) (WriteResult[WikiPage], error)
	DeleteWikiPage(context.Context, DeleteWikiPageRequest, WriteOptions) (WriteResult[WikiPage], error)
	ListMilestones(context.Context, MilestoneListRequest) (Page[Milestone], error)
	ListPushRemoteMirrors(context.Context, PushMirrorListRequest) ([]PushMirror, error)
	TriggerPushRemoteMirror(context.Context, PushMirrorTriggerRequest, WriteOptions) (WriteResult[PushMirror], error)
	GetMilestone(context.Context, MilestoneRequest) (Milestone, error)
	CreateMilestone(context.Context, MilestoneWriteRequest, WriteOptions) (WriteResult[Milestone], error)
	UpdateMilestone(context.Context, MilestoneWriteRequest, WriteOptions) (WriteResult[Milestone], error)
	GetRelease(context.Context, ReleaseRequest) (Release, error)
	CreateRelease(context.Context, ReleaseWriteRequest, WriteOptions) (WriteResult[Release], error)
	UpdateRelease(context.Context, ReleaseWriteRequest, WriteOptions) (WriteResult[Release], error)
}

type ProviderMode string

const (
	ProviderModeLive        ProviderMode = "live"
	ProviderModeFixture     ProviderMode = "fixture"
	ProviderModeUnavailable ProviderMode = "unavailable"
)

const (
	FixtureBoundaryMode = "offline-fixture"
	FixtureIssueMarker  = "ISSUE-42"
	FixtureWikiMarker   = "WIKI-HOME"
)

type FixtureBoundary interface {
	FixtureBoundaryMode() string
	FixtureMarkerIDs() []string
}

func IsFixtureBoundary(v any) bool {
	boundary, ok := v.(FixtureBoundary)
	return ok && boundary.FixtureBoundaryMode() == FixtureBoundaryMode
}

func FixtureMarkerIDs() []string {
	return []string{FixtureIssueMarker, FixtureWikiMarker}
}

type ProviderConfig struct {
	Mode            ProviderMode
	LiveAllowed     bool
	BaseURL         string
	Token           string
	Timeout         time.Duration
	MaxResponseSize int64
	MaxRetries      int
	UserAgent       string
	Pagination      PaginationConfig
	RateLimitRPS    float64
	RateLimitBurst  int
}

type FixtureConfig struct {
	RootDir    string
	Owner      string
	Repo       string
	BaseURL    string
	Scenario   string
	Pagination PaginationConfig
}

type RepoRequest struct {
	Owner string
	Repo  string
}

type Repo struct {
	ID          string    `json:"id"`
	Owner       string    `json:"owner"`
	Name        string    `json:"name"`
	FullName    string    `json:"full_name"`
	DefaultRef  string    `json:"default_ref"`
	Description string    `json:"description"`
	HTMLURL     string    `json:"html_url"`
	UpdatedAt   time.Time `json:"updated_at"`
}

type AuthProbeRequest struct {
	Owner string
	Repo  string
}

type AuthProbeResult struct {
	Authenticated bool      `json:"authenticated"`
	TokenPresent  bool      `json:"token_present"`
	Scopes        []string  `json:"scopes"`
	User          string    `json:"user"`
	CheckedAt     time.Time `json:"checked_at"`
	Mode          string    `json:"mode"`
}

type RedactedCapture struct {
	URL     string              `json:"url,omitempty"`
	Headers map[string][]string `json:"headers,omitempty"`
	Body    []byte              `json:"body,omitempty"`
	Error   string              `json:"error,omitempty"`
}

var newHTTPClientForProvider = NewHTTPClient

type LiveProviderOption func(*liveProvider)

func WithRouteSchemaMatrix(matrix RouteSchemaMatrix) LiveProviderOption {
	return func(lp *liveProvider) {
		lp.matrix = matrix
	}
}

func NewLiveProvider(cfg ProviderConfig, opts ...LiveProviderOption) (Provider, error) {
	baseURL, err := validateLiveProviderConfig(cfg)
	if err != nil {
		return nil, err
	}
	client, err := newHTTPClientForProvider(Config{BaseURL: baseURL, Token: cfg.Token, Timeout: cfg.Timeout, MaxResponseSize: cfg.MaxResponseSize, MaxRetries: cfg.MaxRetries, UserAgent: cfg.UserAgent, Pagination: cfg.Pagination, RateLimitRPS: cfg.RateLimitRPS, RateLimitBurst: cfg.RateLimitBurst})
	if err != nil {
		return nil, err
	}
	lp := liveProvider{HTTPClient: client, matrix: DefaultRouteSchemaMatrix()}
	for _, opt := range opts {
		opt(&lp)
	}
	if err := lp.matrix.ValidateCoverage([]ProductArea{
		ProductAreaIssues, ProductAreaLabels, ProductAreaMilestones,
		ProductAreaPullRequests, ProductAreaComments, ProductAreaWiki,
		ProductAreaPushMirrors,
	}); err != nil {
		return nil, fmt.Errorf("gitcode: live provider route schema matrix validation failed: %w", err)
	}
	return lp, nil
}

func validateLiveProviderConfig(cfg ProviderConfig) (string, error) {
	if cfg.Mode != ProviderModeLive || !cfg.LiveAllowed || strings.TrimSpace(cfg.Token) == "" {
		return "", ErrProviderUnavailable{Reason: "live provider requires live mode, explicit live allowance, and token"}
	}
	base := strings.TrimSpace(cfg.BaseURL)
	if base == "" {
		return "", ErrProviderUnavailable{Reason: "live provider requires selected absolute http or https base URL"}
	}
	parsed, err := url.Parse(base)
	if err != nil || !parsed.IsAbs() || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") {
		return "", ErrProviderUnavailable{Reason: "live provider requires selected absolute http or https base URL"}
	}
	return parsed.String(), nil
}

func NewUnavailableProvider(reason string) Provider {
	return unavailableProvider{reason: reason}
}

func RequireLiveProviderForTest() (string, bool) {
	if os.Getenv("GITCODE_LIVE_TEST") != "1" {
		return "", false
	}
	token := strings.TrimSpace(os.Getenv("GITCODE_LIVE_TOKEN"))
	if token == "" {
		token = strings.TrimSpace(os.Getenv("GITCODE_TEST_TOKEN"))
	}
	return token, token != ""
}

type liveProvider struct {
	*HTTPClient
	matrix RouteSchemaMatrix
}

func (p liveProvider) ProbeAuth(ctx context.Context, req AuthProbeRequest) (AuthProbeResult, error) {
	_, err := p.GetRepo(ctx, RepoRequest{Owner: req.Owner, Repo: req.Repo})
	if err != nil {
		return AuthProbeResult{}, err
	}
	return AuthProbeResult{Authenticated: true, TokenPresent: strings.TrimSpace(p.token) != "", CheckedAt: time.Now().UTC(), Mode: string(ProviderModeLive)}, nil
}

func (p liveProvider) GetRepo(ctx context.Context, req RepoRequest) (Repo, error) {
	var repo Repo
	err := p.getJSON(ctx, getRepoEndpoint(req.Owner, req.Repo), nil, &repo)
	if err != nil {
		return Repo{}, err
	}
	if repo.Owner == "" {
		repo.Owner = req.Owner
	}
	if repo.Name == "" {
		repo.Name = req.Repo
	}
	if repo.FullName == "" {
		repo.FullName = req.Owner + "/" + req.Repo
	}
	return repo, nil
}

func (p liveProvider) ListIssueComments(ctx context.Context, req IssueRequest) (Page[Comment], error) {
	if err := p.matrix.Preflight(ProductAreaComments); err != nil {
		return Page[Comment]{}, err
	}
	return p.HTTPClient.ListIssueComments(ctx, req)
}

func (p liveProvider) CreateIssueComment(ctx context.Context, req CreateIssueCommentRequest, opts WriteOptions) (WriteResult[Comment], error) {
	if err := p.matrix.Preflight(ProductAreaComments); err != nil {
		return WriteResult[Comment]{}, err
	}
	return p.HTTPClient.CreateIssueComment(ctx, req, opts)
}

func (p liveProvider) UpdateIssueComment(ctx context.Context, req UpdateIssueCommentRequest, opts WriteOptions) (WriteResult[Comment], error) {
	if err := p.matrix.Preflight(ProductAreaComments); err != nil {
		return WriteResult[Comment]{}, err
	}
	return p.HTTPClient.UpdateIssueComment(ctx, req, opts)
}

func (p liveProvider) ListPRs(ctx context.Context, req PRListRequest) (Page[PullRequest], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return Page[PullRequest]{}, err
	}
	return p.HTTPClient.ListPRs(ctx, req)
}

func (p liveProvider) GetPR(ctx context.Context, req PRRequest) (PullRequest, error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return PullRequest{}, err
	}
	return p.HTTPClient.GetPR(ctx, req)
}

func (p liveProvider) ListPRComments(ctx context.Context, req PRRequest) (Page[PRComment], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return Page[PRComment]{}, err
	}
	return p.HTTPClient.ListPRComments(ctx, req)
}

func (p liveProvider) CreatePR(ctx context.Context, req CreatePRRequest, opts WriteOptions) (WriteResult[PullRequest], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return WriteResult[PullRequest]{}, err
	}
	return p.HTTPClient.CreatePR(ctx, req, opts)
}

func (p liveProvider) UpdatePR(ctx context.Context, req UpdatePRRequest, opts WriteOptions) (WriteResult[PullRequest], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return WriteResult[PullRequest]{}, err
	}
	return p.HTTPClient.UpdatePR(ctx, req, opts)
}

func (p liveProvider) MergePR(ctx context.Context, req MergePRRequest, opts WriteOptions) (WriteResult[PullRequest], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return WriteResult[PullRequest]{}, err
	}
	return p.HTTPClient.MergePR(ctx, req, opts)
}

func (p liveProvider) LinkPRIssue(ctx context.Context, req LinkPRIssueRequest, opts WriteOptions) (WriteResult[[]Issue], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return WriteResult[[]Issue]{}, err
	}
	return p.HTTPClient.LinkPRIssue(ctx, req, opts)
}

func (p liveProvider) CreatePRComment(ctx context.Context, req CreatePRCommentRequest, opts WriteOptions) (WriteResult[PRComment], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return WriteResult[PRComment]{}, err
	}
	return p.HTTPClient.CreatePRComment(ctx, req, opts)
}

func (p liveProvider) CreatePRReviewComment(ctx context.Context, req CreatePRReviewCommentRequest, opts WriteOptions) (WriteResult[PRComment], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return WriteResult[PRComment]{}, err
	}
	return p.HTTPClient.CreatePRReviewComment(ctx, req, opts)
}

func (p liveProvider) ReplyPRReviewComment(ctx context.Context, req ReplyPRReviewCommentRequest, opts WriteOptions) (WriteResult[PRComment], error) {
	if err := p.matrix.Preflight(ProductAreaPullRequests); err != nil {
		return WriteResult[PRComment]{}, err
	}
	return p.HTTPClient.ReplyPRReviewComment(ctx, req, opts)
}

func (p liveProvider) ListMilestones(ctx context.Context, req MilestoneListRequest) (Page[Milestone], error) {
	if err := p.matrix.Preflight(ProductAreaMilestones); err != nil {
		return Page[Milestone]{}, err
	}
	return p.HTTPClient.ListMilestones(ctx, req)
}

func (p liveProvider) ListPushRemoteMirrors(ctx context.Context, req PushMirrorListRequest) ([]PushMirror, error) {
	if err := p.matrix.Preflight(ProductAreaPushMirrors); err != nil {
		return nil, err
	}
	return p.HTTPClient.ListPushRemoteMirrors(ctx, req)
}

func (p liveProvider) TriggerPushRemoteMirror(ctx context.Context, req PushMirrorTriggerRequest, opts WriteOptions) (WriteResult[PushMirror], error) {
	if err := p.matrix.Preflight(ProductAreaPushMirrors); err != nil {
		return WriteResult[PushMirror]{}, err
	}
	return p.HTTPClient.TriggerPushRemoteMirror(ctx, req, opts)
}

func (p liveProvider) GetMilestone(ctx context.Context, req MilestoneRequest) (Milestone, error) {
	if err := p.matrix.Preflight(ProductAreaMilestones); err != nil {
		return Milestone{}, err
	}
	return p.HTTPClient.GetMilestone(ctx, req)
}

func (p liveProvider) CreateMilestone(ctx context.Context, req MilestoneWriteRequest, opts WriteOptions) (WriteResult[Milestone], error) {
	if err := p.matrix.Preflight(ProductAreaMilestones); err != nil {
		return WriteResult[Milestone]{}, err
	}
	return p.HTTPClient.CreateMilestone(ctx, req, opts)
}

func (p liveProvider) UpdateMilestone(ctx context.Context, req MilestoneWriteRequest, opts WriteOptions) (WriteResult[Milestone], error) {
	if err := p.matrix.Preflight(ProductAreaMilestones); err != nil {
		return WriteResult[Milestone]{}, err
	}
	return p.HTTPClient.UpdateMilestone(ctx, req, opts)
}

func (p liveProvider) AddLabel(ctx context.Context, req LabelRequest, opts WriteOptions) (WriteResult[Issue], error) {
	return WriteResult[Issue]{}, ErrUnsupportedCapability{
		CapabilityKey: "add_label",
		Message:       "add-label is not supported through the live provider: use update-issue --labels instead",
	}
}

func (p liveProvider) GetRelease(ctx context.Context, req ReleaseRequest) (Release, error) {
	return p.HTTPClient.GetRelease(ctx, req)
}

func (p liveProvider) CreateRelease(ctx context.Context, req ReleaseWriteRequest, opts WriteOptions) (WriteResult[Release], error) {
	return p.HTTPClient.CreateRelease(ctx, req, opts)
}

func (p liveProvider) UpdateRelease(ctx context.Context, req ReleaseWriteRequest, opts WriteOptions) (WriteResult[Release], error) {
	return p.HTTPClient.UpdateRelease(ctx, req, opts)
}

type unavailableProvider struct {
	reason string
}

func (p unavailableProvider) err() error {
	if strings.TrimSpace(p.reason) == "" {
		p.reason = "provider unavailable"
	}
	return ErrProviderUnavailable{Reason: p.reason}
}

func (p unavailableProvider) ProbeAuth(context.Context, AuthProbeRequest) (AuthProbeResult, error) {
	return AuthProbeResult{}, p.err()
}
func (p unavailableProvider) GetRepo(context.Context, RepoRequest) (Repo, error) {
	return Repo{}, p.err()
}
func (p unavailableProvider) ListIssues(context.Context, IssueListRequest) (Page[IssueSummary], error) {
	return Page[IssueSummary]{}, p.err()
}
func (p unavailableProvider) GetIssue(context.Context, IssueRequest) (Issue, error) {
	return Issue{}, p.err()
}
func (p unavailableProvider) ListIssueComments(context.Context, IssueRequest) (Page[Comment], error) {
	return Page[Comment]{}, p.err()
}
func (p unavailableProvider) ListPRs(context.Context, PRListRequest) (Page[PullRequest], error) {
	return Page[PullRequest]{}, p.err()
}
func (p unavailableProvider) GetPR(context.Context, PRRequest) (PullRequest, error) {
	return PullRequest{}, p.err()
}
func (p unavailableProvider) ListPRComments(context.Context, PRRequest) (Page[PRComment], error) {
	return Page[PRComment]{}, p.err()
}
func (p unavailableProvider) CreatePR(context.Context, CreatePRRequest, WriteOptions) (WriteResult[PullRequest], error) {
	return WriteResult[PullRequest]{}, p.err()
}
func (p unavailableProvider) UpdatePR(context.Context, UpdatePRRequest, WriteOptions) (WriteResult[PullRequest], error) {
	return WriteResult[PullRequest]{}, p.err()
}
func (p unavailableProvider) MergePR(context.Context, MergePRRequest, WriteOptions) (WriteResult[PullRequest], error) {
	return WriteResult[PullRequest]{}, p.err()
}
func (p unavailableProvider) LinkPRIssue(context.Context, LinkPRIssueRequest, WriteOptions) (WriteResult[[]Issue], error) {
	return WriteResult[[]Issue]{}, p.err()
}
func (p unavailableProvider) ListWikiPages(context.Context, WikiListRequest) (Page[WikiPage], error) {
	return Page[WikiPage]{}, p.err()
}
func (p unavailableProvider) GetWikiPage(context.Context, WikiPageRequest) (WikiPage, error) {
	return WikiPage{}, p.err()
}
func (p unavailableProvider) Search(context.Context, SearchRequest) (Page[SearchResult], error) {
	return Page[SearchResult]{}, p.err()
}
func (p unavailableProvider) CreateIssue(context.Context, CreateIssueRequest, WriteOptions) (WriteResult[Issue], error) {
	return WriteResult[Issue]{}, p.err()
}
func (p unavailableProvider) UpdateIssue(context.Context, UpdateIssueRequest, WriteOptions) (WriteResult[Issue], error) {
	return WriteResult[Issue]{}, p.err()
}
func (p unavailableProvider) CreateIssueComment(context.Context, CreateIssueCommentRequest, WriteOptions) (WriteResult[Comment], error) {
	return WriteResult[Comment]{}, p.err()
}
func (p unavailableProvider) UpdateIssueComment(context.Context, UpdateIssueCommentRequest, WriteOptions) (WriteResult[Comment], error) {
	return WriteResult[Comment]{}, p.err()
}
func (p unavailableProvider) CreatePRComment(context.Context, CreatePRCommentRequest, WriteOptions) (WriteResult[PRComment], error) {
	return WriteResult[PRComment]{}, p.err()
}
func (p unavailableProvider) CreatePRReviewComment(context.Context, CreatePRReviewCommentRequest, WriteOptions) (WriteResult[PRComment], error) {
	return WriteResult[PRComment]{}, p.err()
}
func (p unavailableProvider) ReplyPRReviewComment(context.Context, ReplyPRReviewCommentRequest, WriteOptions) (WriteResult[PRComment], error) {
	return WriteResult[PRComment]{}, p.err()
}
func (p unavailableProvider) CreateWikiPage(context.Context, CreateWikiPageRequest, WriteOptions) (WriteResult[WikiPage], error) {
	return WriteResult[WikiPage]{}, p.err()
}
func (p unavailableProvider) UpdateWikiPage(context.Context, UpdateWikiPageRequest, WriteOptions) (WriteResult[WikiPage], error) {
	return WriteResult[WikiPage]{}, p.err()
}
func (p unavailableProvider) DeleteWikiPage(context.Context, DeleteWikiPageRequest, WriteOptions) (WriteResult[WikiPage], error) {
	return WriteResult[WikiPage]{}, p.err()
}

func (p unavailableProvider) ListMilestones(context.Context, MilestoneListRequest) (Page[Milestone], error) {
	return Page[Milestone]{}, p.err()
}
func (p unavailableProvider) ListPushRemoteMirrors(context.Context, PushMirrorListRequest) ([]PushMirror, error) {
	return nil, p.err()
}
func (p unavailableProvider) TriggerPushRemoteMirror(context.Context, PushMirrorTriggerRequest, WriteOptions) (WriteResult[PushMirror], error) {
	return WriteResult[PushMirror]{}, p.err()
}
func (p unavailableProvider) GetMilestone(context.Context, MilestoneRequest) (Milestone, error) {
	return Milestone{}, p.err()
}
func (p unavailableProvider) CreateMilestone(context.Context, MilestoneWriteRequest, WriteOptions) (WriteResult[Milestone], error) {
	return WriteResult[Milestone]{}, p.err()
}
func (p unavailableProvider) UpdateMilestone(context.Context, MilestoneWriteRequest, WriteOptions) (WriteResult[Milestone], error) {
	return WriteResult[Milestone]{}, p.err()
}
func (p unavailableProvider) GetRelease(context.Context, ReleaseRequest) (Release, error) {
	return Release{}, p.err()
}
func (p unavailableProvider) CreateRelease(context.Context, ReleaseWriteRequest, WriteOptions) (WriteResult[Release], error) {
	return WriteResult[Release]{}, p.err()
}
func (p unavailableProvider) UpdateRelease(context.Context, ReleaseWriteRequest, WriteOptions) (WriteResult[Release], error) {
	return WriteResult[Release]{}, p.err()
}

func IsProviderUnavailable(err error) bool {
	var target ErrProviderUnavailable
	return errors.As(err, &target)
}