package app

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

	"github.com/tidbcloud/AgentTeam/internal/core/webhook"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/app/webhooks"
	"github.com/tidbcloud/AgentTeam/internal/store/provider"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// serviceEventProviderWebhookEvent is the normalized provider webhook occurrence used by
// the dispatch pipeline after provider-specific verification/parsing succeeds.
type serviceEventProviderWebhookEvent struct {
	Installation             webhookAppInstallation
	Event                    provider.ProviderEvent
	DeliveryKey              string
	EventType                string
	ServiceConnectionID      string
	ServiceWebhookEndpointID string
}

func (store *Store) normalizeServiceEventProviderWebhookEvent(ctx context.Context, app webhooks.AppDefinition, headers http.Header, payload []byte) (serviceEventProviderWebhookEvent, bool, error) {
	switch strings.ToLower(strings.TrimSpace(app.AppSlug)) {
	case PullRequestSubscriptionProviderGitHub:
		return store.normalizeGitHubServiceEventProviderWebhookEvent(ctx, app, headers, payload)
	case PullRequestSubscriptionProviderGitea:
		return store.normalizeGiteaServiceEventProviderWebhookEvent(ctx, app, headers, payload)
	default:
		return serviceEventProviderWebhookEvent{}, false, badRequest("provider app webhook is not supported")
	}
}

func (store *Store) normalizeGitHubServiceEventProviderWebhookEvent(ctx context.Context, app webhooks.AppDefinition, headers http.Header, payload []byte) (serviceEventProviderWebhookEvent, bool, error) {
	connection, found, err := store.loadVerifiedGitHubWebhookConnection(ctx, app, headers, payload)
	if err != nil || !found {
		return serviceEventProviderWebhookEvent{}, false, err
	}
	installation := connection.webhookAppInstallation
	event, parsed, err := parseGitHubWebhookPayload(payload)
	if err != nil {
		return serviceEventProviderWebhookEvent{}, false, badRequest("invalid GitHub webhook payload")
	}
	if !parsed {
		return serviceEventProviderWebhookEvent{}, false, err
	}
	eventType := normalizeGitHubWebhookEventType(headers.Get("X-GitHub-Event"), event.Action)
	deliveryKey := githubWebhookDeliveryKey(headers.Get("X-GitHub-Delivery"), eventType, payload)
	normalized, ok, err := webhook.NormalizeGitHubProviderEvent(
		installation.WorkspaceID,
		installation.ExternalSubjectRef,
		deliveryKey,
		eventType,
		event,
		time.Now().UTC(),
	)
	if err != nil || !ok {
		return serviceEventProviderWebhookEvent{}, ok, err
	}
	serviceConnectionID, endpointID, err := store.providerWebhookServiceReceiptRefs(ctx, connection)
	if err != nil {
		return serviceEventProviderWebhookEvent{}, false, err
	}
	return serviceEventProviderWebhookEvent{
		Installation:             installation,
		Event:                    providerEventFromCore(normalized),
		DeliveryKey:              deliveryKey,
		EventType:                eventType,
		ServiceConnectionID:      serviceConnectionID,
		ServiceWebhookEndpointID: endpointID,
	}, true, nil
}

func (store *Store) normalizeGiteaServiceEventProviderWebhookEvent(ctx context.Context, app webhooks.AppDefinition, headers http.Header, payload []byte) (serviceEventProviderWebhookEvent, bool, error) {
	connection, found, err := store.loadVerifiedGiteaWebhookConnection(ctx, app, headers, payload)
	if err != nil || !found {
		return serviceEventProviderWebhookEvent{}, false, err
	}
	installation := connection.webhookAppInstallation
	event, parsed, err := parseGiteaWebhookPayload(payload)
	if err != nil {
		return serviceEventProviderWebhookEvent{}, false, badRequest("invalid Gitea webhook payload")
	}
	if !parsed {
		return serviceEventProviderWebhookEvent{}, false, err
	}
	eventType := normalizeGiteaWebhookEventType(headers.Get("X-Gitea-Event"), event.Action)
	deliveryKey := giteaWebhookDeliveryKey(headers.Get("X-Gitea-Delivery"), eventType, payload)
	instanceURL := ""
	if config, found, err := store.providerStore().LoadGiteaAppConfigForWorkspace(ctx, installation.WorkspaceID); err == nil && found {
		instanceURL = config.InstanceURL
	} else if err != nil {
		return serviceEventProviderWebhookEvent{}, false, err
	}
	normalized, ok, err := webhook.NormalizeGiteaProviderEvent(
		installation.WorkspaceID,
		installation.ExternalSubjectRef,
		deliveryKey,
		eventType,
		event,
		time.Now().UTC(),
		instanceURL,
	)
	if err != nil || !ok {
		return serviceEventProviderWebhookEvent{}, ok, err
	}
	serviceConnectionID, endpointID, err := store.providerWebhookServiceReceiptRefs(ctx, connection)
	if err != nil {
		return serviceEventProviderWebhookEvent{}, false, err
	}
	return serviceEventProviderWebhookEvent{
		Installation:             installation,
		Event:                    providerEventFromCore(normalized),
		DeliveryKey:              deliveryKey,
		EventType:                eventType,
		ServiceConnectionID:      serviceConnectionID,
		ServiceWebhookEndpointID: endpointID,
	}, true, nil
}

func (store *Store) providerWebhookServiceReceiptRefs(ctx context.Context, connection webhookAppConnection) (string, string, error) {
	installationID := strings.TrimSpace(connection.InstallationID)
	externalSubjectRef := strings.TrimSpace(connection.ExternalSubjectRef)
	if installationID == "" || externalSubjectRef == "" {
		return "", "", nil
	}

	var serviceConnection serviceactor.Connection
	if err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		txStore := store.serviceActorStore().InTx(tx)
		loaded, found, err := txStore.LoadActiveConnectionForUpdate(ctx, installationID, externalSubjectRef)
		if err != nil {
			return err
		}
		if !found {
			return nil
		}
		serviceConnection = loaded
		return store.syncAppServiceWebhookEndpoint(ctx, tx, serviceConnection, serviceConnection.Credentials, nil, time.Now().UTC())
	}); err != nil {
		return "", "", err
	}
	if strings.TrimSpace(serviceConnection.ID) == "" {
		return "", "", nil
	}
	endpointID, err := store.providerWebhookEndpointID(ctx, serviceConnection.ID)
	if err != nil {
		return "", "", err
	}
	return strings.TrimSpace(serviceConnection.ID), endpointID, nil
}

func (store *Store) providerWebhookEndpointID(ctx context.Context, connectionID string) (string, error) {
	if strings.TrimSpace(connectionID) == "" {
		return "", nil
	}
	endpoint, err := store.serviceActorStore().LoadWebhookEndpointForConnection(ctx, connectionID, serviceactor.EndpointKindProviderWebhook)
	if err != nil {
		if errors.Is(err, serviceactor.ErrNotFound) {
			return "", nil
		}
		return "", err
	}
	return strings.TrimSpace(endpoint.ID), nil
}
