package org

import (
	"context"
	"log/slog"
	"net/http"
	"strings"
	"time"

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

const (
	orgHostedModelsObject = "organization_hosted_model.list"

	orgHostedAvailabilityStatusAvailable   = "available"
	orgHostedAvailabilityStatusUnavailable = "unavailable"

	orgHostedReasonTemporarilyUnavailable = "temporarily_unavailable"
)

// ModelsList handles GET /api/v1/orgs/:orgId/hosted-models.
// It returns MaaS-backed hosted inventory and organization-level availability
// facts for owner/admin management surfaces.
func (handler *HostedModelHandler) ModelsList(r api.AuthenticatedRequest) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	if organizationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "orgId is required")
	}

	if _, err := orgs.GetOrganizationHostedModelSettingsForUser(r.Context(), r.UserID(), organizationID); err != nil {
		return api.Response{}, err
	}

	response := unavailableOrganizationHostedModelsResponse(organizationID)
	if handler.hostedModels == nil {
		return api.OK(response), nil
	}

	availability, err := handler.hostedModels.LoadHostedAvailability(r.Context(), organizationID)
	if err != nil {
		logOrganizationHostedAvailabilityFailure(r.Context(), handler.logger, organizationID, err)
		return api.OK(response), nil
	}

	response.HostedAvailability = dto.SelectableHostedAvailabilitySummary{
		Status:               orgHostedAvailabilityStatusAvailable,
		CreditAtomsPerCredit: maasavailability.CreditAtomsPerCredit,
	}
	response.EntitlementStatus = strings.TrimSpace(availability.EntitlementStatus)
	response.BalanceStatus = strings.TrimSpace(availability.BalanceStatus)
	response.AvailableCreditAtoms = availability.AvailableCreditAtoms
	response.CreditUnit = strings.TrimSpace(availability.CreditUnit)
	response.UpdatedAt = hostedAvailabilityUpdatedAt(availability.UpdatedAt)
	response.Models = organizationHostedModelViews(availability)
	return api.OK(response), nil
}

// SettingsGet handles GET /api/v1/orgs/:orgId/hosted-model-settings.
// It returns the organization-wide hosted model picker and auto-authorization settings.
func (handler *HostedModelHandler) SettingsGet(r api.AuthenticatedRequest) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	if organizationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "orgId is required")
	}

	settings, err := orgs.GetOrganizationHostedModelSettingsForUser(r.Context(), r.UserID(), organizationID)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToOrgHostedModelSettingsView(settings)), nil
}

// SettingsPatch handles PATCH /api/v1/orgs/:orgId/hosted-model-settings.
// It updates the organization-wide hosted model picker and auto-authorization settings.
func (handler *HostedModelHandler) SettingsPatch(r api.AuthenticatedJSON[dto.OrganizationHostedModelSettingsPatchRequest]) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	if organizationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "orgId is required")
	}

	body := r.Body
	settings, err := orgs.PatchOrganizationHostedModelSettingsForUser(r.Context(), r.UserID(), organizationID, updateOrganizationHostedModelSettingsParams(body))
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToOrgHostedModelSettingsView(settings)), nil
}

// WorkspaceAuthorizationGet handles GET /api/v1/orgs/:orgId/workspaces/:workspaceId/hosted-model-authorization.
// It returns the requested workspace's desired hosted model access.
func (handler *HostedModelHandler) WorkspaceAuthorizationGet(r api.AuthenticatedRequest) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	workspaceID := strings.TrimSpace(r.Gin.Param("workspaceId"))
	if organizationID == "" || workspaceID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	authorization, err := orgs.GetOrganizationWorkspaceHostedModelAuthorizationForUser(r.Context(), r.UserID(), organizationID, workspaceID)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToOrgWorkspaceHostedModelAuthorizationView(authorization)), nil
}

// WorkspaceAuthorizationPreview handles POST /api/v1/orgs/:orgId/workspaces/:workspaceId/hosted-model-authorization/preview.
// It returns the hosted-agent count that would be affected by the requested
// workspace hosted authorization change without applying the change.
func (handler *HostedModelHandler) WorkspaceAuthorizationPreview(r api.AuthenticatedJSON[dto.OrganizationWorkspaceHostedModelAuthorizationPutRequest]) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	workspaceID := strings.TrimSpace(r.Gin.Param("workspaceId"))
	if organizationID == "" || workspaceID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	authorization, err := orgs.PreviewOrganizationWorkspaceHostedModelAuthorizationForUser(r.Context(), r.UserID(), organizationID, workspaceID, upsertOrganizationWorkspaceHostedModelAuthorizationParams(r.Body))
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToOrgWorkspaceHostedModelAuthorizationView(authorization)), nil
}

func unavailableOrganizationHostedModelsResponse(organizationID string) dto.OrganizationHostedModelsResponse {
	return dto.OrganizationHostedModelsResponse{
		Object:         orgHostedModelsObject,
		OrganizationID: strings.TrimSpace(organizationID),
		Models:         []dto.OrganizationHostedModelView{},
		HostedAvailability: dto.SelectableHostedAvailabilitySummary{
			Status:  orgHostedAvailabilityStatusUnavailable,
			Reasons: []string{orgHostedReasonTemporarilyUnavailable},
		},
	}
}

func organizationHostedModelViews(availability maasavailability.Availability) []dto.OrganizationHostedModelView {
	models := make([]dto.OrganizationHostedModelView, 0, len(availability.Models))
	for _, model := range availability.Models {
		modelRef := strings.TrimSpace(model.ModelReference)
		if modelRef == "" {
			continue
		}
		displayName := strings.TrimSpace(model.DisplayName)
		if displayName == "" {
			displayName = modelRef
		}
		models = append(models, dto.OrganizationHostedModelView{
			HostedModelID:                    strings.TrimSpace(model.HostedModelID),
			ModelReference:                   modelRef,
			DisplayName:                      displayName,
			Enabled:                          model.Enabled,
			Reasons:                          normalizeOrganizationHostedStrings(model.Reasons),
			InventoryStatus:                  strings.TrimSpace(model.InventoryStatus),
			EntitlementStatus:                strings.TrimSpace(model.EntitlementStatus),
			AccessSource:                     strings.TrimSpace(model.AccessSource),
			BalanceStatus:                    strings.TrimSpace(model.BalanceStatus),
			RuntimeStatus:                    strings.TrimSpace(model.RuntimeStatus),
			PricingFormulaVersion:            strings.TrimSpace(model.PricingFormulaVersion),
			BillableDimensions:               normalizeOrganizationHostedStrings(model.BillableDimensions),
			ContextTiers:                     organizationHostedPricingTiers(model.ContextTiers),
			SupportedFamilies:                normalizeOrganizationHostedStrings(model.SupportedFamilies),
			ExecutableWireAPIFamilies:        normalizeOrganizationHostedStrings(model.ExecutableWireAPIFamilies),
			MessagesFeatures:                 normalizeOrganizationHostedStrings(model.MessagesFeatures),
			ChatCompletionsFeatures:          normalizeOrganizationHostedStrings(model.ChatCompletionsFeatures),
			ResponsesFeatures:                normalizeOrganizationHostedStrings(model.ResponsesFeatures),
			InputCreditAtomsPerMillionTokens: model.InputCreditAtomsPerMillionTokens,
			CacheReadInputCreditAtomsPerMillionTokens: model.CacheReadInputCreditAtomsPerMillionTokens,
			OutputCreditAtomsPerMillionTokens:         model.OutputCreditAtomsPerMillionTokens,
			UpdatedAt:                                 availability.UpdatedAt,
		})
	}
	return models
}

func organizationHostedPricingTiers(tiers []maasavailability.PricingTier) []dto.HostedModelPricingTierResponse {
	if len(tiers) == 0 {
		return nil
	}
	presented := make([]dto.HostedModelPricingTierResponse, 0, len(tiers))
	for _, tier := range tiers {
		presented = append(presented, dto.HostedModelPricingTierResponse{
			ID:               strings.TrimSpace(tier.ID),
			MinContextTokens: tier.MinContextTokens,
			MaxContextTokens: tier.MaxContextTokens,
			Rates: dto.HostedModelPricingTierRatesResponse{
				InputTokensCreditAtomsPerMillionTokens:    tier.Rates.InputTokensCreditAtomsPerMillionTokens,
				CacheReadInputCreditAtomsPerMillionTokens: tier.Rates.CacheReadInputCreditAtomsPerMillionTokens,
				OutputCreditAtomsPerMillionTokens:         tier.Rates.OutputCreditAtomsPerMillionTokens,
			},
		})
	}
	return presented
}

func hostedAvailabilityUpdatedAt(updatedAt time.Time) *time.Time {
	if updatedAt.IsZero() {
		return nil
	}
	return &updatedAt
}

func normalizeOrganizationHostedStrings(items []string) []string {
	normalized := make([]string, 0, len(items))
	seen := make(map[string]struct{}, len(items))
	for _, item := range items {
		trimmed := strings.TrimSpace(item)
		if trimmed == "" {
			continue
		}
		if _, ok := seen[trimmed]; ok {
			continue
		}
		seen[trimmed] = struct{}{}
		normalized = append(normalized, trimmed)
	}
	return normalized
}

func logOrganizationHostedAvailabilityFailure(ctx context.Context, logger *slog.Logger, organizationID string, err error) {
	if logger == nil {
		return
	}
	logger.WarnContext(
		ctx,
		"load organization hosted model availability",
		slog.String("organizationID", strings.TrimSpace(organizationID)),
		slog.Any("error", err),
	)
}

// WorkspaceAuthorizationPut handles PUT /api/v1/orgs/:orgId/workspaces/:workspaceId/hosted-model-authorization.
// It replaces the requested workspace's desired hosted model access.
func (handler *HostedModelHandler) WorkspaceAuthorizationPut(r api.AuthenticatedJSON[dto.OrganizationWorkspaceHostedModelAuthorizationPutRequest]) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	workspaceID := strings.TrimSpace(r.Gin.Param("workspaceId"))
	if organizationID == "" || workspaceID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	body := r.Body
	authorization, err := orgs.UpsertOrganizationWorkspaceHostedModelAuthorizationForUser(r.Context(), r.UserID(), organizationID, workspaceID, upsertOrganizationWorkspaceHostedModelAuthorizationParams(body))
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToOrgWorkspaceHostedModelAuthorizationView(authorization)), nil
}
