package app

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

	"github.com/tidbcloud/AgentTeam/internal/core/webhook"
	"github.com/tidbcloud/AgentTeam/internal/store/app/webhooks"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

type webhookAppInstallation struct {
	model.RuntimeAppInstallation
	ExternalSubjectRef string
	Credentials        []byte
}

type githubPullRequestResource struct {
	providerPullRequestResource
	RepositoryFullName string
	Number             int
}

func (store *Store) HandleAppWebhook(ctx context.Context, appSlug string, headers http.Header, payload []byte) (AppWebhookResult, error) {
	app, err := store.webhookStore().LoadAppDefinition(ctx, appSlug)
	if err != nil {
		return AppWebhookResult{}, err
	}
	if !isPullRequestSubscriptionProviderSlug(app.AppSlug) {
		return AppWebhookResult{}, ErrNotFound
	}
	return safeHandleProviderWebhook(ctx, store.logger, serviceEventProviderAppHandler{store: store}, app, headers, payload)
}

func (store *Store) loadVerifiedGitHubWebhookConnection(ctx context.Context, app webhooks.AppDefinition, headers http.Header, payload []byte) (webhookAppConnection, bool, error) {
	externalSubjectRef, parsedForRouting := extractGitHubInstallationRef(payload)
	if strings.TrimSpace(externalSubjectRef) == "" || !parsedForRouting {
		return webhookAppConnection{}, false, nil
	}

	connection, found, err := store.loadVerifiedConnectionByExternalRef(ctx, app.AppID, externalSubjectRef, func(candidate webhookAppConnection) (bool, error) {
		if config, configFound, err := store.providerStore().LoadGitHubAppConfigForWorkspace(ctx, candidate.WorkspaceID); err != nil {
			return false, err
		} else if configFound && verifyGitHubWebhookSignature(headers.Get("X-Hub-Signature-256"), payload, config.WebhookSecret) {
			return true, nil
		}

		webhookSecret, err := extractWebhookSecret(candidate.Credentials)
		if err != nil {
			return false, err
		}
		return verifyGitHubWebhookSignature(headers.Get("X-Hub-Signature-256"), payload, webhookSecret), nil
	})
	if err != nil {
		return webhookAppConnection{}, false, err
	}
	if !found {
		return webhookAppConnection{}, false, nil
	}
	return connection, true, nil
}

func (store *Store) loadVerifiedConnectionByExternalRef(
	ctx context.Context,
	appID string,
	externalSubjectRef string,
	verify func(webhookAppConnection) (bool, error),
) (webhookAppConnection, bool, error) {
	connections, err := store.loadEnabledConnectionsByExternalRef(ctx, appID, externalSubjectRef)
	if err != nil {
		return webhookAppConnection{}, false, err
	}
	if len(connections) == 0 {
		return webhookAppConnection{}, false, nil
	}
	for _, connection := range connections {
		matched, err := verify(connection)
		if err != nil {
			return webhookAppConnection{}, false, err
		}
		if matched {
			return connection, true, nil
		}
	}
	return webhookAppConnection{}, false, ErrUnauthorized
}

func extractGitHubInstallationRef(payload []byte) (string, bool) {
	return webhook.ExtractGitHubInstallationRef(payload)
}

func extractWebhookSecret(credentials []byte) (string, error) {
	secret, err := webhook.ExtractWebhookSecret(credentials)
	if errors.Is(err, webhook.ErrWebhookSecretNotFound) {
		return "", ErrUnauthorized
	}
	return secret, err
}

func stringValue(value any) string {
	return webhook.StringValue(value)
}

func verifyGitHubWebhookSignature(signatureHeader string, payload []byte, secret string) bool {
	return webhook.VerifyGitHubWebhookSignature(signatureHeader, payload, secret)
}

func githubWebhookDeliveryKey(deliveryHeader string, eventType string, payload []byte) string {
	return webhook.GitHubWebhookDeliveryKey(deliveryHeader, eventType, payload)
}

func payloadExcerptForLog(payload []byte) string {
	return webhook.PayloadExcerptForLog(payload)
}

func parseGitHubWebhookPayload(payload []byte) (webhook.GitHubPayload, bool, error) {
	return webhook.ParseGitHubWebhookPayload(payload)
}

func normalizeGitHubWebhookEventType(eventHeader string, action string) string {
	return webhook.NormalizeGitHubWebhookEventType(eventHeader, action)
}

func githubPullRequestResourceFromPullRequest(event webhook.GitHubPayload) (githubPullRequestResource, bool) {
	resource, ok := webhook.GitHubPullRequestResourceFromPullRequest(event)
	return githubPullRequestResourceFromService(resource), ok
}

func githubPullRequestResourceFromIssueComment(event webhook.GitHubPayload) (githubPullRequestResource, bool, bool) {
	resource, ok, isPullRequestComment := webhook.GitHubPullRequestResourceFromIssueComment(event)
	return githubPullRequestResourceFromService(resource), ok, isPullRequestComment
}

func githubPullRequestResourceFromNumber(repositoryFullName string, numberValue string, resourceURL string) (githubPullRequestResource, bool) {
	resource, ok := webhook.GitHubPullRequestResourceFromNumberString(repositoryFullName, numberValue, resourceURL)
	return githubPullRequestResourceFromService(resource), ok
}

func githubPullRequestResourceFromService(resource webhook.PullRequestResource) githubPullRequestResource {
	return githubPullRequestResource{
		providerPullRequestResource: providerPullRequestResource{
			ResourceType:       resource.ResourceType,
			ResourceIdentifier: resource.ResourceIdentifier,
			ResourceURL:        resource.ResourceURL,
		},
		RepositoryFullName: resource.RepositoryFullName,
		Number:             resource.Number,
	}
}
