package app

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/store/app/webhooks"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

var (
	ErrPullRequestProviderAuthUnavailable = errors.New("pull request provider authorization unavailable")
	ErrPullRequestProviderRateLimited     = errors.New("pull request provider rate limited")
	ErrPullRequestProviderUpstreamFailure = errors.New("pull request provider upstream failure")
)

const pullRequestResourceValidationTimeout = 5 * time.Second

func validatePullRequestSubscriptionResource(
	ctx context.Context,
	store *Store,
	workspaceID string,
	installationID string,
	resource PullRequestSubscriptionResource,
) error {
	if store == nil {
		return ErrPullRequestProviderUpstreamFailure
	}
	if strings.TrimSpace(installationID) == "" {
		return storeerr.BadRequest("installationId is required")
	}
	validationCtx, cancel := context.WithTimeout(
		ctx,
		pullRequestResourceValidationTimeout,
	)
	defer cancel()
	connectionRef, status, err := resolvePullRequestSubscriptionConnectionForInstallation(
		validationCtx,
		store,
		resource.Provider,
		strings.TrimSpace(installationID),
		resource.Owner+"/"+resource.Repo,
		"",
	)
	if err != nil {
		return err
	}
	if status != webhooks.ServiceEventConnectionReady ||
		strings.TrimSpace(connectionRef) == "" {
		return ErrPullRequestProviderAuthUnavailable
	}
	var lookupErr error
	switch resource.Provider {
	case PullRequestSubscriptionProviderGitea:
		// A ready Gitea repository/organization connection is the authority for
		// inbound webhook subscriptions. Do not add a pull-request-read PAT scope
		// solely to validate a watch: Gitea may return 404 for private pulls when
		// the token can administer repository hooks but cannot read issues/pulls.
		// The connection boundary already prevents subscribing outside the
		// configured repository or organization.
		return nil
	case PullRequestSubscriptionProviderGitHub:
		lookupErr = lookupGitHubPullRequest(
			validationCtx,
			store,
			workspaceID,
			connectionRef,
			resource,
		)
	default:
		return storeerr.BadRequest("pull request provider is not supported")
	}
	if errors.Is(lookupErr, context.DeadlineExceeded) {
		return ErrPullRequestProviderUpstreamFailure
	}
	return lookupErr
}

func lookupGitHubPullRequest(
	ctx context.Context,
	store *Store,
	workspaceID string,
	connectionRef string,
	resource PullRequestSubscriptionResource,
) error {
	config, found, err := store.providerStore().LoadGitHubAppConfigForWorkspace(
		ctx,
		strings.TrimSpace(workspaceID),
	)
	if err != nil {
		return err
	}
	if !found {
		return ErrPullRequestProviderAuthUnavailable
	}
	token, err := store.createGitHubInstallationAccessToken(ctx, config, connectionRef)
	if err != nil {
		return fmt.Errorf("%w: provider credential request failed", ErrPullRequestProviderUpstreamFailure)
	}
	requestURL := fmt.Sprintf(
		"%s/repos/%s/%s/pulls/%d",
		strings.TrimRight(store.githubAPIBaseURL, "/"),
		url.PathEscape(resource.Owner),
		url.PathEscape(resource.Repo),
		resource.Number,
	)
	request, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)
	if err != nil {
		return fmt.Errorf("%w: build GitHub pull request lookup", ErrPullRequestProviderUpstreamFailure)
	}
	request.Header.Set("Accept", "application/vnd.github+json")
	request.Header.Set("Authorization", "Bearer "+token)
	statusCode, transportErr := webhooks.PerformPullRequestResourceLookup(store.appHTTPClient, request)
	if transportErr != nil {
		return fmt.Errorf("%w: pull request lookup transport failed", ErrPullRequestProviderUpstreamFailure)
	}
	return classifyPullRequestProviderStatus(statusCode)
}

func classifyPullRequestProviderStatus(statusCode int) error {
	switch {
	case statusCode >= http.StatusOK && statusCode < http.StatusMultipleChoices:
		return nil
	case statusCode == http.StatusNotFound:
		return storeerr.NotFound("pull_request_not_found")
	case statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden:
		return ErrPullRequestProviderAuthUnavailable
	case statusCode == http.StatusTooManyRequests:
		return ErrPullRequestProviderRateLimited
	default:
		return ErrPullRequestProviderUpstreamFailure
	}
}
