package router

import (
	"fmt"
	"sort"
	"strings"

	"github.com/gin-gonic/gin"
)

// EdgePolicy describes how the public Edge Gateway should route a server route.
type EdgePolicy string

const (
	EdgePolicyCentralREST       EdgePolicy = "central-rest"
	EdgePolicyWorkspaceREST     EdgePolicy = "workspace-rest"
	EdgePolicyCentralException  EdgePolicy = "central-exception"
	EdgePolicyWorkspaceExplicit EdgePolicy = "workspace-explicit"
	EdgePolicyLegacyRuntime     EdgePolicy = "legacy-runtime"
	EdgePolicyUnsupported       EdgePolicy = "unsupported"
	EdgePolicyHealth            EdgePolicy = "health"
	EdgePolicyInternalOnly      EdgePolicy = "internal-only"
)

// EdgeRouteContract is the reviewable route ownership contract consumed by
// deployment validation.
type EdgeRouteContract struct {
	Profile string     `json:"profile"`
	Method  string     `json:"method"`
	Path    string     `json:"path"`
	Policy  EdgePolicy `json:"policy"`
}

// EdgePolicyForRoute classifies one registered route into the production edge
// routing policy.
func EdgePolicyForRoute(profile Profile, method string, path string) (EdgePolicy, bool) {
	path = strings.TrimSpace(path)
	if path == "" {
		return "", false
	}
	if path == "/healthz" || path == "/readyz" {
		return EdgePolicyHealth, true
	}
	if strings.HasPrefix(path, "/debug/pprof") {
		return EdgePolicyInternalOnly, true
	}
	if strings.HasPrefix(path, "/internal/admin/") {
		return EdgePolicyInternalOnly, true
	}
	if path == "/internal/workspaces/:workspaceId/app-notifications" || strings.HasPrefix(path, "/internal/workspaces/:workspaceId/app-notifications/") {
		return EdgePolicyInternalOnly, true
	}

	switch profile {
	case ProfileCentral:
		if isCentralEdgeException(method, path) {
			return EdgePolicyCentralException, true
		}
		if isAPIRoute(path) {
			return EdgePolicyCentralREST, true
		}
	case ProfileWorkspace:
		if path == "/api/graphql" {
			return EdgePolicyUnsupported, true
		}
		if isLegacyRuntimeEdgeRoute(path) {
			return EdgePolicyLegacyRuntime, true
		}
		if isWorkspaceExplicitEdgeRoute(path) {
			return EdgePolicyWorkspaceExplicit, true
		}
		if isAPIRoute(path) {
			return EdgePolicyWorkspaceREST, true
		}
	}

	return "", false
}

// BuildEdgeRouteContractForRouters builds a sorted route contract for the split
// production profiles.
func BuildEdgeRouteContractForRouters(centralRouter *gin.Engine, workspaceRouter *gin.Engine) ([]EdgeRouteContract, error) {
	contracts := make([]EdgeRouteContract, 0)
	for _, item := range []struct {
		profile Profile
		router  *gin.Engine
	}{
		{profile: ProfileCentral, router: centralRouter},
		{profile: ProfileWorkspace, router: workspaceRouter},
	} {
		if item.router == nil {
			return nil, fmt.Errorf("build edge route contract: %s router is nil", item.profile)
		}
		for _, route := range item.router.Routes() {
			policy, ok := EdgePolicyForRoute(item.profile, route.Method, route.Path)
			if !ok {
				return nil, fmt.Errorf("build edge route contract: unclassified route profile=%s method=%s path=%s", item.profile, route.Method, route.Path)
			}
			contracts = append(contracts, EdgeRouteContract{
				Profile: string(item.profile),
				Method:  route.Method,
				Path:    route.Path,
				Policy:  policy,
			})
		}
	}

	sort.Slice(contracts, func(i, j int) bool {
		left := contracts[i]
		right := contracts[j]
		if left.Profile != right.Profile {
			return left.Profile < right.Profile
		}
		if left.Path != right.Path {
			return left.Path < right.Path
		}
		return left.Method < right.Method
	})
	return contracts, nil
}

func isAPIRoute(path string) bool {
	return path == "/api" || strings.HasPrefix(path, "/api/")
}

func isCentralEdgeException(method string, path string) bool {
	return path == "/api/github/callback" ||
		path == "/api/github/setup" ||
		path == "/api/github/setup/start"
}

func isWorkspaceExplicitEdgeRoute(path string) bool {
	return strings.HasPrefix(path, "/w/") ||
		strings.HasPrefix(path, "/api/llm/") ||
		path == "/api/apps/github/webhook" ||
		path == "/api/apps/gitea/webhook"
}

func isLegacyRuntimeEdgeRoute(path string) bool {
	return strings.HasPrefix(path, "/daemon") ||
		strings.HasPrefix(path, "/internal/agent/") ||
		strings.HasPrefix(path, "/internal/app/runtime")
}
