package admin

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

	"github.com/gin-gonic/gin"

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

const (
	adminWindow24h = "24h"
	adminWindow7d  = "7d"
	adminWindow30d = "30d"

	adminHostedCreditUnitAgentTeamCredit    = "agentteam_credit"
	adminHostedCreditGrantOperatorGift = "operator_gift"

	adminHostedCreditGrantUnavailable  = "hosted_credit_grant_unavailable"
	adminHostedCreditGrantInvalidInput = "hosted_credit_grant_invalid_input"
	adminHostedCreditGrantConflict     = "hosted_credit_grant_conflict"
)

// SystemAdminHandler owns system-admin dashboard and registration allowlist routes.
type SystemAdminHandler struct {
	adminStore              AdminStore
	allowlistStore          RegistrationAllowlistStore
	creditGrants            HostedCreditGrantClient
	dbPool                  DatabasePoolController
	platformServiceAccounts PlatformServiceAccountService
}

func parseAdminWindow(raw string) (string, time.Duration) {
	switch strings.ToLower(strings.TrimSpace(raw)) {
	case adminWindow24h:
		return adminWindow24h, 24 * time.Hour
	case adminWindow30d:
		return adminWindow30d, 30 * 24 * time.Hour
	default:
		return adminWindow7d, 7 * 24 * time.Hour
	}
}

func parseAdminLimit(raw string, fallback int) int {
	if strings.TrimSpace(raw) == "" {
		return fallback
	}
	limit, err := strconv.Atoi(strings.TrimSpace(raw))
	if err != nil {
		return fallback
	}
	return limit
}

func adminBoolQuery(ctx *gin.Context, key string) bool {
	value := strings.TrimSpace(ctx.Query(key))
	return value == "1" || strings.EqualFold(value, "true")
}

func parseOptionalDuration(raw *string, field string) (*time.Duration, error) {
	if raw == nil {
		return nil, nil
	}
	parsed, err := time.ParseDuration(strings.TrimSpace(*raw))
	if err != nil {
		return nil, api.NewHTTPError(http.StatusBadRequest, field+" must be a valid Go duration")
	}
	return &parsed, nil
}

// Overview handles GET /api/v1/admin/overview.
// It returns aggregated system overview metrics for the admin dashboard.
func (handler *SystemAdminHandler) Overview(r api.AuthenticatedRequest) (api.Response, error) {
	windowLabel, windowDuration := parseAdminWindow(r.Gin.Query("window"))
	overview, err := handler.adminStore.LoadAdminOverview(r.Context(), r.UserID(), time.Now().UTC().Add(-windowDuration))
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(dto.AdminOverviewResponse{
		Window:   windowLabel,
		Overview: converter.ToAdminOverview(overview),
	}), nil
}

// ListWorkspaces handles GET /api/v1/admin/workspaces.
// It returns a filtered and paginated list of workspaces for the admin dashboard.
func (handler *SystemAdminHandler) ListWorkspaces(r api.AuthenticatedRequest) (api.Response, error) {
	_, windowDuration := parseAdminWindow(r.Gin.Query("window"))
	items, err := handler.adminStore.ListAdminWorkspaces(
		r.Context(),
		r.UserID(),
		r.Gin.Query("query"),
		time.Now().UTC().Add(-windowDuration),
		adminBoolQuery(r.Gin, "active"),
		parseAdminLimit(r.Gin.Query("limit"), 100),
	)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToAdminWorkspaceSummaries(items)), nil
}

// ListUsers handles GET /api/v1/admin/users.
// It returns a filtered and paginated list of users for the admin dashboard.
func (handler *SystemAdminHandler) ListUsers(r api.AuthenticatedRequest) (api.Response, error) {
	_, windowDuration := parseAdminWindow(r.Gin.Query("window"))
	items, err := handler.adminStore.ListAdminUsers(
		r.Context(),
		r.UserID(),
		r.Gin.Query("query"),
		time.Now().UTC().Add(-windowDuration),
		adminBoolQuery(r.Gin, "active"),
		parseAdminLimit(r.Gin.Query("limit"), 100),
	)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToAdminUserSummaries(items)), nil
}

// ListRegistrationAllowlist handles GET /api/v1/admin/registration-allowlist.
// It returns all registration allowlist entries for the admin dashboard.
func (handler *SystemAdminHandler) ListRegistrationAllowlist(r api.AuthenticatedRequest) (api.Response, error) {
	items, err := handler.allowlistStore.ListRegistrationAllowlistEntriesForUser(
		r.Context(),
		r.UserID(),
		r.Gin.Query("query"),
		adminBoolQuery(r.Gin, "includeDisabled"),
	)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToAdminRegistrationAllowlistEntries(items)), nil
}

// CreateRegistrationAllowlist handles POST /api/v1/admin/registration-allowlist.
// It creates a new registration allowlist entry.
func (handler *SystemAdminHandler) CreateRegistrationAllowlist(r api.AuthenticatedJSON[dto.AdminRegistrationAllowlistCreateRequest]) (api.Response, error) {
	request := r.Body
	var expiresAt *time.Time
	trimmedExpiresAt := strings.TrimSpace(request.ExpiresAt)
	if trimmedExpiresAt != "" {
		parsed, err := time.Parse(time.RFC3339, trimmedExpiresAt)
		if err != nil {
			return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "expiresAt must be RFC3339")
		}
		parsedUTC := parsed.UTC()
		expiresAt = &parsedUTC
	}
	entry, err := handler.allowlistStore.CreateRegistrationAllowlistEntryForUser(
		r.Context(),
		r.UserID(),
		request.Pattern,
		request.Reason,
		expiresAt,
	)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToAdminRegistrationAllowlistEntry(entry)), nil
}

// DisableRegistrationAllowlist handles POST /api/v1/admin/registration-allowlist/:entryId/disable.
// It disables the specified registration allowlist entry.
func (handler *SystemAdminHandler) DisableRegistrationAllowlist(r api.AuthenticatedRequest) (api.Response, error) {
	entryID := strings.TrimSpace(r.Gin.Param("entryId"))
	if entryID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "entryId is required")
	}
	entry, err := handler.allowlistStore.DisableRegistrationAllowlistEntryForUser(r.Context(), r.UserID(), entryID)
	if err != nil {
		return api.Response{}, err
	}
	return api.OK(converter.ToAdminRegistrationAllowlistEntry(entry)), nil
}

// LoadDatabasePool handles GET /api/v1/admin/runtime/db-pool.
func (handler *SystemAdminHandler) LoadDatabasePool(_ api.AuthenticatedRequest) (api.Response, error) {
	if handler.dbPool == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Database pool controller not configured")
	}
	return api.OK(converter.ToAdminDBPoolResponse(handler.dbPool.Snapshot())), nil
}

// PatchDatabasePool handles PATCH /api/v1/admin/runtime/db-pool.
func (handler *SystemAdminHandler) PatchDatabasePool(r api.AuthenticatedJSON[dto.AdminDBPoolPatchRequest]) (api.Response, error) {
	if handler.dbPool == nil {
		return api.Response{}, api.NewHTTPError(http.StatusInternalServerError, "Database pool controller not configured")
	}

	current := handler.dbPool.Snapshot()
	next := current.Config
	request := r.Body

	if request.MaxOpenConns != nil {
		next.MaxOpenConns = *request.MaxOpenConns
	}
	if request.MaxIdleConns != nil {
		next.MaxIdleConns = *request.MaxIdleConns
	}
	connMaxLifetime, err := parseOptionalDuration(request.ConnMaxLifetime, "connMaxLifetime")
	if err != nil {
		return api.Response{}, err
	}
	if connMaxLifetime != nil {
		next.ConnMaxLifetime = *connMaxLifetime
	}
	connMaxIdleTime, err := parseOptionalDuration(request.ConnMaxIdleTime, "connMaxIdleTime")
	if err != nil {
		return api.Response{}, err
	}
	if connMaxIdleTime != nil {
		next.ConnMaxIdleTime = *connMaxIdleTime
	}

	snapshot, err := handler.dbPool.Update(next)
	if err != nil {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, err.Error())
	}
	return api.OK(converter.ToAdminDBPoolResponse(snapshot)), nil
}

// GrantHostedCredit handles POST /api/v1/admin/orgs/:orgId/hosted-credit-grants.
// It authorizes a platform operator in AgentTeam, then forwards the idempotent grant
// request to agentteam-maas, which owns hosted credit persistence and ledger effects.
func (handler *SystemAdminHandler) GrantHostedCredit(r api.AuthenticatedJSON[dto.AdminHostedCreditGrantCreateRequest]) (api.Response, error) {
	if handler.creditGrants == nil {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusServiceUnavailable, "Hosted credit grant service unavailable", adminHostedCreditGrantUnavailable)
	}
	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	if organizationID == "" {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "orgId is required", adminHostedCreditGrantInvalidInput)
	}
	idempotencyKey := strings.TrimSpace(r.Gin.GetHeader("Idempotency-Key"))
	if idempotencyKey == "" {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "Idempotency-Key is required", adminHostedCreditGrantInvalidInput)
	}

	body := r.Body
	if err := validateAdminHostedCreditAmount(body); err != nil {
		return api.Response{}, err
	}
	reasonRef := strings.TrimSpace(body.GrantReasonRef)
	if reasonRef == "" {
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusBadRequest, "grantReasonRef is required", adminHostedCreditGrantInvalidInput)
	}
	sourceType := strings.TrimSpace(body.GrantSourceType)
	if sourceType == "" {
		sourceType = adminHostedCreditGrantOperatorGift
	}
	expiresAt, err := parseOptionalRFC3339(body.ExpiresAt, "expiresAt")
	if err != nil {
		return api.Response{}, err
	}

	grant, err := handler.creditGrants.GrantHostedCredit(r.Context(), HostedCreditGrantParams{
		OrganizationID:    organizationID,
		IdempotencyKey:    idempotencyKey,
		CreditUnit:        adminHostedCreditUnitAgentTeamCredit,
		CreditAmount:      body.CreditAmount,
		CreditAmountAtoms: body.CreditAmountAtoms,
		GrantSourceType:   sourceType,
		GrantReasonRef:    reasonRef,
		Refundable:        body.Refundable,
		ExpiresAt:         expiresAt,
		OperatorIdentity:  r.UserID(),
		SourceRef:         strings.TrimSpace(body.SourceRef),
	})
	if err != nil {
		return api.Response{}, mapHostedCreditGrantDependencyError(err)
	}

	response := presentAdminHostedCreditGrant(grant)
	if grant.Created {
		return api.Created(response), nil
	}
	return api.OK(response), nil
}

func presentAdminHostedCreditGrant(grant HostedCreditGrant) dto.AdminHostedCreditGrantResponse {
	return dto.AdminHostedCreditGrantResponse{
		GrantID:           grant.GrantID,
		OperationID:       grant.OperationID,
		Created:           grant.Created,
		OrganizationID:    grant.OrganizationID,
		CreditUnit:        grant.CreditUnit,
		CreditAmount:      grant.CreditAmount,
		CreditAmountAtoms: grant.CreditAmountAtoms,
		GrantSourceType:   grant.GrantSourceType,
		GrantReasonRef:    grant.GrantReasonRef,
		Refundable:        grant.Refundable,
		ExpiresAt:         grant.ExpiresAt,
		OperatorIdentity:  grant.OperatorIdentity,
		SourceRef:         grant.SourceRef,
		CreatedAt:         grant.CreatedAt,
		UpdatedAt:         grant.UpdatedAt,
	}
}

func validateAdminHostedCreditAmount(body dto.AdminHostedCreditGrantCreateRequest) error {
	hasCredits := body.CreditAmount > 0
	hasAtoms := body.CreditAmountAtoms > 0
	if hasCredits == hasAtoms {
		return api.NewHTTPErrorWithCode(http.StatusBadRequest, "provide exactly one of creditAmount or creditAmountAtoms", adminHostedCreditGrantInvalidInput)
	}
	return nil
}

func parseOptionalRFC3339(raw string, field string) (*time.Time, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil, nil
	}
	parsed, err := time.Parse(time.RFC3339, trimmed)
	if err != nil {
		return nil, api.NewHTTPErrorWithCode(http.StatusBadRequest, field+" must be RFC3339", adminHostedCreditGrantInvalidInput)
	}
	parsedUTC := parsed.UTC()
	return &parsedUTC, nil
}

func mapHostedCreditGrantDependencyError(err error) error {
	var serviceErr HostedCreditGrantServiceError
	if errors.As(err, &serviceErr) {
		switch serviceErr.StatusCode {
		case http.StatusBadRequest:
			return api.NewHTTPErrorWithCodeAndCause(http.StatusBadRequest, "Invalid hosted credit grant request", adminHostedCreditGrantInvalidInput, err)
		case http.StatusConflict:
			return api.NewHTTPErrorWithCodeAndCause(http.StatusConflict, "Hosted credit grant idempotency conflict", adminHostedCreditGrantConflict, err)
		case http.StatusNotFound:
			return api.NewHTTPErrorWithCodeAndCause(http.StatusNotFound, "Organization not found", adminHostedCreditGrantInvalidInput, err)
		}
	}
	return api.NewHTTPErrorWithCodeAndCause(http.StatusBadGateway, "Hosted credit grant service unavailable", adminHostedCreditGrantUnavailable, err)
}
