package knowledgeqa

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/knowledgeqa/protocol"
	maasavailability "github.com/tidbcloud/AgentTeam/internal/maas/availability"
)

// GetPolicyStatus exposes the organization switch and current AgentTeam-hosted
// model inventory to workspace members. Model choice is made when a session is
// created, not in Settings.
func (handler *policyHandler) GetPolicyStatus(request api.WorkspaceScopedRequest) (api.Response, error) {
	if handler.deps.Policies == nil || handler.deps.Organizations == nil {
		return api.Response{}, policyUnavailable()
	}
	organizationSummary, err := handler.deps.Organizations.GetWorkspaceOrganizationForUser(
		request.Context(), request.UserID(), request.WorkspaceID(),
	)
	if err != nil {
		return api.Response{}, err
	}
	policy, err := handler.deps.Policies.LoadOrganizationPolicy(
		request.Context(), organizationSummary.ID,
	)
	if err != nil {
		return api.Response{}, err
	}
	status := "organization_disabled"
	models := []dto.KnowledgeQAHostedModelOption{}
	if handler.deps.Queue == nil || handler.deps.HostedAvailability == nil {
		status = "runtime_unavailable"
	} else if availability, loadErr := handler.deps.HostedAvailability.LoadHostedAvailability(
		request.Context(), organizationSummary.ID,
	); loadErr != nil {
		status = "runtime_unavailable"
	} else {
		models = hostedModelOptions(availability)
		status = hostedAvailabilityStatus(availability, models)
	}
	if !policy.Enabled {
		status = "organization_disabled"
	}
	reasonLevels, defaultReasonLevel := hostedReasonLevelPolicy(models)
	return api.OK(dto.KnowledgeQAPolicyStatusResponse{
		Enabled:                   policy.Enabled && status == "available",
		OrganizationEnabled:       policy.Enabled,
		Status:                    status,
		Runtime:                   "server_hosted",
		ManagedBy:                 "organization",
		WorkloadKind:              "knowledge_qa",
		OrganizationPolicyVersion: policy.PolicyVersion,
		Models:                    models,
		ReasonLevels:              reasonLevels,
		DefaultReasonLevel:        defaultReasonLevel,
	}), nil
}

func hostedModelOptions(availability maasavailability.Availability) []dto.KnowledgeQAHostedModelOption {
	models := make([]dto.KnowledgeQAHostedModelOption, 0, len(availability.Models))
	for _, model := range availability.Models {
		reference := strings.TrimSpace(model.ModelReference)
		if reference == "" {
			continue
		}
		enabled := hostedModelExecutable(model)
		var reasonLevels []string
		defaultReasonLevel := ""
		if enabled {
			reasonLevels, defaultReasonLevel, enabled = hostedModelReasonLevels(model)
		}
		models = append(models, dto.KnowledgeQAHostedModelOption{
			ModelReference:     reference,
			DisplayName:        strings.TrimSpace(model.DisplayName),
			Enabled:            enabled,
			Reasons:            append([]string(nil), model.Reasons...),
			ReasonLevels:       append([]string(nil), reasonLevels...),
			DefaultReasonLevel: defaultReasonLevel,
		})
	}
	return models
}

func hostedReasonLevelPolicy(models []dto.KnowledgeQAHostedModelOption) ([]string, string) {
	available := make(map[string]struct{}, 3)
	for _, model := range models {
		if !model.Enabled {
			continue
		}
		for _, level := range model.ReasonLevels {
			available[strings.TrimSpace(level)] = struct{}{}
		}
	}
	levels := make([]string, 0, 3)
	for _, level := range []string{"low", "medium", "high"} {
		if _, ok := available[level]; ok {
			levels = append(levels, level)
		}
	}
	defaultLevel := ""
	if len(levels) > 0 {
		defaultLevel = levels[0]
	}
	for _, level := range levels {
		if level == "medium" {
			defaultLevel = level
			break
		}
		if level == "high" {
			defaultLevel = level
		}
	}
	return levels, defaultLevel
}

func hostedAvailabilityStatus(
	availability maasavailability.Availability,
	models []dto.KnowledgeQAHostedModelOption,
) string {
	if strings.TrimSpace(availability.EntitlementStatus) != maasavailability.EntitlementStatusEligible {
		return "entitlement_invalid"
	}
	if strings.TrimSpace(availability.BalanceStatus) != maasavailability.BalanceStatusPositive {
		return "credits_insufficient"
	}
	for _, model := range models {
		if model.Enabled {
			return "available"
		}
	}
	return "model_unavailable"
}

func hostedPolicyStatus(availability maasavailability.Availability, modelReference string, reasonLevel string) string {
	if strings.TrimSpace(availability.EntitlementStatus) != maasavailability.EntitlementStatusEligible {
		return "entitlement_invalid"
	}
	if strings.TrimSpace(availability.BalanceStatus) != maasavailability.BalanceStatusPositive {
		return "credits_insufficient"
	}
	modelReference = strings.TrimSpace(modelReference)
	for _, model := range availability.Models {
		if strings.TrimSpace(model.ModelReference) != modelReference {
			continue
		}
		if strings.TrimSpace(model.EntitlementStatus) != maasavailability.EntitlementStatusEligible {
			return "entitlement_invalid"
		}
		if strings.TrimSpace(model.BalanceStatus) != maasavailability.BalanceStatusPositive {
			return "credits_insufficient"
		}
		if hostedModelExecutable(model) {
			levels, _, ok := hostedModelReasonLevels(model)
			if ok && containsHostedCapability(levels, reasonLevel) {
				return "available"
			}
		}
		return "model_unavailable"
	}
	return "model_unavailable"
}

func hostedModelExecutable(model maasavailability.Model) bool {
	if !(model.Enabled &&
		strings.TrimSpace(model.InventoryStatus) == maasavailability.InventoryStatusActive &&
		strings.TrimSpace(model.EntitlementStatus) == maasavailability.EntitlementStatusEligible &&
		strings.TrimSpace(model.BalanceStatus) == maasavailability.BalanceStatusPositive &&
		strings.TrimSpace(model.RuntimeStatus) == maasavailability.RuntimeStatusSupported &&
		containsHostedCapability(model.ExecutableWireAPIFamilies, "chat_completions")) {
		return false
	}
	if model.ChatCompletionsCompatibilityProfile == nil ||
		!model.ChatCompletionsCompatibilityProfile.SupportsKnowledgeQAFeatures(model.ChatCompletionsFeatures) {
		return false
	}
	_, _, ok := hostedModelReasonLevels(model)
	return ok
}

func hostedModelReasonLevels(model maasavailability.Model) ([]string, string, bool) {
	if model.ChatCompletionsCompatibilityProfile == nil {
		return nil, "", false
	}
	return model.ChatCompletionsCompatibilityProfile.KnowledgeQAReasonLevels()
}

func containsHostedCapability(values []string, target string) bool {
	for _, value := range values {
		if strings.TrimSpace(value) == target {
			return true
		}
	}
	return false
}

func (handler *handlerBase) resolveSubmissionPolicy(
	ctx context.Context,
	userID string,
	workspaceID string,
	model string,
	reasonLevel string,
) (Organization, protocol.EffectivePolicy, error) {
	if handler.deps.Policies == nil || handler.deps.Organizations == nil {
		return Organization{}, protocol.EffectivePolicy{}, policyUnavailable()
	}
	if handler.deps.Queue == nil {
		return Organization{}, protocol.EffectivePolicy{},
			api.NewHTTPErrorWithCode(
				http.StatusServiceUnavailable,
				"Knowledge Q&A runtime is unavailable",
				"knowledge_qa_runtime_unavailable",
			)
	}
	summary, err := handler.deps.Organizations.GetWorkspaceOrganizationForUser(
		ctx, userID, workspaceID,
	)
	if err != nil {
		return Organization{}, protocol.EffectivePolicy{}, err
	}
	effective, err := handler.deps.Policies.ResolveEffectivePolicy(
		ctx, summary.ID, model, reasonLevel, "",
	)
	if err != nil {
		switch {
		case errors.Is(err, protocol.ErrOrganizationDisabled):
			return Organization{}, protocol.EffectivePolicy{},
				api.NewHTTPErrorWithCode(
					http.StatusForbidden,
					"Knowledge Q&A is disabled for this organization",
					"knowledge_qa_organization_disabled",
				)
		case errors.Is(err, protocol.ErrPolicyUnavailable):
			return Organization{}, protocol.EffectivePolicy{},
				policyUnavailable()
		default:
			return Organization{}, protocol.EffectivePolicy{}, err
		}
	}
	if handler.deps.HostedAvailability == nil {
		return Organization{}, protocol.EffectivePolicy{},
			api.NewHTTPErrorWithCode(
				http.StatusServiceUnavailable,
				"Knowledge Q&A hosted models are unavailable",
				"knowledge_qa_runtime_unavailable",
			)
	}
	availability, err := handler.deps.HostedAvailability.LoadHostedAvailability(ctx, summary.ID)
	if err != nil {
		return Organization{}, protocol.EffectivePolicy{},
			api.NewHTTPErrorWithCode(
				http.StatusServiceUnavailable,
				"Knowledge Q&A hosted models are unavailable",
				"knowledge_qa_runtime_unavailable",
			)
	}
	if status := hostedPolicyStatus(availability, effective.Model, effective.ReasonLevel); status != "available" {
		return Organization{}, protocol.EffectivePolicy{},
			hostedStatusError(status)
	}
	return summary, effective, nil
}

func hostedStatusError(status string) error {
	switch status {
	case "entitlement_invalid":
		return api.NewHTTPErrorWithCode(
			http.StatusForbidden,
			"Knowledge Q&A hosted-model entitlement is unavailable",
			"knowledge_qa_entitlement_invalid",
		)
	case "credits_insufficient":
		return api.NewHTTPErrorWithCode(
			http.StatusPaymentRequired,
			"Knowledge Q&A hosted credits are insufficient",
			"knowledge_qa_credits_insufficient",
		)
	default:
		return api.NewHTTPErrorWithCode(
			http.StatusServiceUnavailable,
			"The selected Knowledge Q&A model is unavailable",
			"knowledge_qa_model_unavailable",
		)
	}
}
