package server

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"

	agsserver "github.com/pingcap/agent-git-service/server"

	"github.com/tidbcloud/AgentTeam/internal/store/content"
	"github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	"gorm.io/gorm"
)

// workspaceAGSRepositoryAuthority keeps the canonical workspace AGS seam on
// one exact repository lookup. Product bindings opt into the mutation cutoff
// here; the router remains unaware of product-specific Content Store keys.
type workspaceAGSRepositoryAuthority struct {
	repositories *repos.Store
	content      *content.Store
}

const maxAGSGraphQLMutationBodyBytes = 1 << 20

type agsGraphQLMutationRepositoryResolver interface {
	ResolveGraphQLMutationRepositories(context.Context, []byte) ([]string, bool, error)
}

var _ agsGraphQLMutationRepositoryResolver = (*agsserver.Server)(nil)

func agsGraphQLMutationResolver(server agsServer) agsGraphQLMutationRepositoryResolver {
	resolver, _ := server.(agsGraphQLMutationRepositoryResolver)
	return resolver
}

func newWorkspaceAGSRepositoryAuthority(database *gorm.DB) *workspaceAGSRepositoryAuthority {
	return &workspaceAGSRepositoryAuthority{
		repositories: repos.New(database),
		content:      content.New(database),
	}
}

func (authority *workspaceAGSRepositoryAuthority) IsBoundToWorkspace(ctx context.Context, workspaceID, repository string) (bool, error) {
	if authority == nil || authority.repositories == nil {
		return false, fmt.Errorf("workspace repository authority is not configured")
	}
	return authority.repositories.IsBoundToWorkspace(ctx, workspaceID, repository)
}

// AllowLegacyRepositoryMutation returns false once the exact Automation
// binding is native-active. The authority row is both the native owner switch
// and the ingress cutoff: requests admitted before the switch may finish, but
// every later REST or Git write observes native ownership and is rejected.
func (authority *workspaceAGSRepositoryAuthority) AllowLegacyRepositoryMutation(ctx context.Context, workspaceID, repository string) (bool, error) {
	if authority == nil || authority.repositories == nil || authority.content == nil {
		return false, fmt.Errorf("workspace repository mutation authority is not configured")
	}
	workspaceID = strings.TrimSpace(workspaceID)
	repository = strings.TrimSpace(repository)
	if workspaceID == "" || repository == "" {
		return false, fmt.Errorf("workspace repository mutation identity is invalid")
	}

	resourceID := repos.AutomationRepositoryResourceID(repository)
	binding, err := authority.repositories.Get(
		ctx,
		workspaceID,
		repos.ResourceTypeAutomationRepository,
		resourceID,
		repos.RepoRolePrimary,
	)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return true, nil
	}
	if err != nil {
		return false, fmt.Errorf("resolve Automation repository mutation binding: %w", err)
	}
	if !strings.EqualFold(strings.TrimSpace(binding.RepoFullName), repository) {
		return false, fmt.Errorf("Automation repository mutation binding does not match canonical repository")
	}
	return authority.allowLegacyAutomationAuthority(ctx, binding.WorkspaceID, binding.RepoFullName)
}

func (authority *workspaceAGSRepositoryAuthority) allowLegacyAutomationAuthority(ctx context.Context, workspaceID, bindingKey string) (bool, error) {
	contentAuthority, found, err := authority.content.GetAuthority(ctx, strings.TrimSpace(workspaceID), "automation", strings.TrimSpace(bindingKey))
	if err != nil {
		return false, fmt.Errorf("resolve Automation content mutation authority: %w", err)
	}
	if !found || contentAuthority.State == content.AuthorityStateLegacyActive {
		return true, nil
	}
	if contentAuthority.State == content.AuthorityStateNativeActive {
		return false, nil
	}
	return false, fmt.Errorf("Automation content mutation authority state is invalid")
}

// legacyRepositoryMutationCutoff is the canonical shared ingress gate for
// every registered repository write, including old global REST and Git Smart
// HTTP clients. It runs before AGS admission, so a request that already passed
// the check may finish while every request arriving after native activation is
// rejected. Reads and repositories without an Automation binding are unchanged.
func legacyRepositoryMutationCutoff(next http.Handler, authority *workspaceAGSRepositoryAuthority, graphql agsGraphQLMutationRepositoryResolver) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		route, ok := agsserver.MatchRegisteredRoute(request)
		if !ok {
			next.ServeHTTP(writer, request)
			return
		}
		repositories := []string(nil)
		switch {
		case route.Repository != "" && route.Capability == "repo:write":
			repositories = []string{route.Repository}
		case route.Method == http.MethodPost && (route.Pattern == "/api/graphql" || route.Pattern == "/graphql"):
			if graphql == nil {
				http.Error(writer, "GraphQL repository mutation authority is unavailable", http.StatusServiceUnavailable)
				return
			}
			body, err := io.ReadAll(io.LimitReader(request.Body, maxAGSGraphQLMutationBodyBytes+1))
			if err != nil {
				http.Error(writer, "GraphQL repository mutation authority is unavailable", http.StatusServiceUnavailable)
				return
			}
			request.Body = io.NopCloser(bytes.NewReader(body))
			if len(body) > maxAGSGraphQLMutationBodyBytes {
				http.Error(writer, "GraphQL request body is too large", http.StatusRequestEntityTooLarge)
				return
			}
			var mutation bool
			repositories, mutation, err = graphql.ResolveGraphQLMutationRepositories(request.Context(), body)
			if err != nil {
				http.Error(writer, "GraphQL repository mutation authority is unavailable", http.StatusServiceUnavailable)
				return
			}
			if !mutation || len(repositories) == 0 {
				next.ServeHTTP(writer, request)
				return
			}
		default:
			next.ServeHTTP(writer, request)
			return
		}

		if authority == nil || authority.repositories == nil || authority.content == nil {
			http.Error(writer, "repository mutation authority is unavailable", http.StatusServiceUnavailable)
			return
		}
		for _, repository := range repositories {
			binding, found, err := authority.repositories.ResolveUniqueAutomationRepository(request.Context(), repository)
			if err != nil {
				http.Error(writer, "repository mutation authority is unavailable", http.StatusServiceUnavailable)
				return
			}
			if !found {
				continue
			}
			allowed, err := authority.allowLegacyAutomationAuthority(request.Context(), binding.WorkspaceID, binding.RepoFullName)
			if err != nil {
				http.Error(writer, "repository mutation authority is unavailable", http.StatusServiceUnavailable)
				return
			}
			if !allowed {
				http.Error(writer, "repository legacy mutations are disabled", http.StatusConflict)
				return
			}
		}
		next.ServeHTTP(writer, request)
	})
}
