package serverruntime

import (
	"context"
	"errors"

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/shared/pkg/contract"
	sharedialect "github.com/nexuscrm/shared/pkg/dialect"
	sharederrors "github.com/nexuscrm/shared/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
	apihostdialect "github.com/pingcap-inc/nexuscrm-api-host/dialect"
	"github.com/pingcap-inc/nexuscrm-api-host/hostruntime"
)

type apiHostDialectAdapter struct {
	legacy sharedialect.Dialect
}

func apiHostDialectFromShared(d sharedialect.Dialect) apihostdialect.Dialect {
	if d == nil {
		return nil
	}
	return apiHostDialectAdapter{legacy: d}
}

// AdaptDialectForAPIHost bridges the current shared dialect contract into the
// extracted api-host transport contract. Product hooks should still expose
// ProductHooks.Dialects; this adapter exists for direct registry callers while
// the legacy shared dialect package is being retired.
func AdaptDialectForAPIHost(d sharedialect.Dialect) apihostdialect.Dialect {
	return apiHostDialectFromShared(d)
}

func apiHostDialectsFromShared(in []sharedialect.Dialect) []apihostdialect.Dialect {
	out := make([]apihostdialect.Dialect, 0, len(in))
	for _, d := range in {
		out = append(out, apiHostDialectFromShared(d))
	}
	return out
}

func (d apiHostDialectAdapter) Name() string       { return d.legacy.Name() }
func (d apiHostDialectAdapter) OwningPack() string { return d.legacy.OwningPack() }
func (d apiHostDialectAdapter) BasePath() string   { return d.legacy.BasePath() }
func (d apiHostDialectAdapter) Versions() []string {
	return append([]string(nil), d.legacy.Versions()...)
}

func (d apiHostDialectAdapter) Manifest() []apihostdialect.Route {
	legacy := d.legacy.Manifest()
	out := make([]apihostdialect.Route, 0, len(legacy))
	for _, route := range legacy {
		out = append(out, apiHostRouteFromShared(route))
	}
	return out
}

func (d apiHostDialectAdapter) ErrorProjector() apihostdialect.Projector {
	projector := d.legacy.ErrorProjector()
	if projector == nil {
		return nil
	}
	return func(req *apihostdialect.Request, err *apihostdialect.DomainWireError) (int, any, bool) {
		return projector(sharedRequestFromAPIHost(req), sharedDomainWireErrorFromAPIHost(err))
	}
}

func (d apiHostDialectAdapter) InternalErrorProjector() apihostdialect.InternalErrorProjector {
	projector := d.legacy.InternalErrorProjector()
	if projector == nil {
		return nil
	}
	return func(req *apihostdialect.Request) (int, any) {
		return projector(sharedRequestFromAPIHost(req))
	}
}

func apiHostRouteFromShared(route sharedialect.Route) apihostdialect.Route {
	return apihostdialect.Route{
		Method:         route.Method,
		Path:           route.Path,
		Gate:           apiHostGateFromShared(route.Gate),
		BodyMode:       apiHostBodyModeFromShared(route.BodyMode),
		BodyLimitBytes: route.BodyLimitBytes,
		Handler:        apiHostHandlerFromShared(route.Handler),
	}
}

func apiHostGateFromShared(gate sharedialect.Gate) apihostdialect.Gate {
	perms := make([]apihostdialect.Permission, 0, len(gate.Permissions))
	for _, perm := range gate.Permissions {
		perms = append(perms, apihostdialect.Permission(perm))
	}
	return apihostdialect.Gate{
		Mode:        apiHostGateModeFromShared(gate.Mode),
		Permissions: perms,
	}
}

func apiHostGateModeFromShared(mode sharedialect.GateMode) apihostdialect.GateMode {
	switch mode {
	case sharedialect.GateNone:
		return apihostdialect.GateNone
	case sharedialect.GateAll:
		return apihostdialect.GateAll
	case sharedialect.GateAny:
		return apihostdialect.GateAny
	default:
		return apihostdialect.GateMode(mode)
	}
}

func apiHostBodyModeFromShared(mode sharedialect.RequestBodyMode) apihostdialect.RequestBodyMode {
	switch mode {
	case sharedialect.BodyBuffered:
		return apihostdialect.BodyBuffered
	case sharedialect.BodyStream:
		return apihostdialect.BodyStream
	default:
		return apihostdialect.RequestBodyMode(mode)
	}
}

func apiHostHandlerFromShared(handler sharedialect.Handler) apihostdialect.Handler {
	if handler == nil {
		return nil
	}
	return func(ctx context.Context, req *apihostdialect.Request, deps apihostdialect.Deps) (*apihostdialect.Response, error) {
		resp, err := handler(ctx, sharedRequestFromAPIHost(req), sharedDialectDepsFromAPIHost(deps))
		if err != nil {
			err = apiHostErrorFromShared(err)
		}
		return apiHostResponseFromShared(resp), err
	}
}

func sharedDialectDepsFromAPIHost(deps apihostdialect.Deps) sharedialect.Deps {
	legacy := sharedialect.Deps{
		Dispatch: sharedDispatcherFromAPIHost(deps.Dispatch),
	}
	switch contracts := deps.Contracts.(type) {
	case contract.Deps:
		legacy.Contracts = contracts
	case *contract.Deps:
		if contracts != nil {
			legacy.Contracts = *contracts
		}
	}
	return legacy
}

func sharedRequestFromAPIHost(req *apihostdialect.Request) *sharedialect.Request {
	if req == nil {
		return nil
	}
	return &sharedialect.Request{
		Version:       req.Version,
		User:          sharedUserSessionFromAPIHost(req.User),
		Params:        cloneStringMap(req.Params),
		Query:         cloneStringSliceMap(req.Query),
		Header:        cloneStringSliceMap(req.Header),
		Body:          append([]byte(nil), req.Body...),
		BodyStream:    req.BodyStream,
		ContentLength: req.ContentLength,
	}
}

func sharedUserSessionFromAPIHost(user *hostruntime.UserSession) *models.UserSession {
	if user == nil {
		return nil
	}
	email := user.Email
	return &models.UserSession{
		ID:             user.ID,
		Name:           user.Name,
		Email:          &email,
		ProfileID:      user.ProfileID,
		ProfileName:    user.ProfileName,
		RoleID:         user.RoleID,
		RoleName:       user.RoleName,
		ConnectedAppId: user.ConnectedAppID,
		Scope:          user.Scope,
		GrantType:      user.GrantType,
		SessionClass:   user.SessionClass,
	}
}

func apiHostUserSessionFromShared(user *models.UserSession) *hostruntime.UserSession {
	if user == nil {
		return nil
	}
	email := ""
	if user.Email != nil {
		email = *user.Email
	}
	return &hostruntime.UserSession{
		ID:             user.ID,
		Name:           user.Name,
		Email:          email,
		ProfileID:      user.ProfileID,
		ProfileName:    user.ProfileName,
		RoleID:         user.RoleID,
		RoleName:       user.RoleName,
		ConnectedAppID: user.ConnectedAppId,
		Scope:          user.Scope,
		GrantType:      user.GrantType,
		SessionClass:   user.SessionClass,
	}
}

func apiHostUserExtractorFromShared(extract func(*gin.Context) *models.UserSession) func(*gin.Context) *hostruntime.UserSession {
	return func(c *gin.Context) *hostruntime.UserSession {
		if extract == nil {
			return nil
		}
		return apiHostUserSessionFromShared(extract(c))
	}
}

func sharedDispatcherFromAPIHost(dispatch apihostdialect.Dispatcher) sharedialect.Dispatcher {
	if dispatch == nil {
		return nil
	}
	return func(ctx context.Context, req sharedialect.DispatchRequest) (*sharedialect.DispatchResponse, error) {
		resp, err := dispatch(ctx, apihostdialect.DispatchRequest{
			Method: req.Method,
			Path:   req.Path,
			Header: cloneStringMap(req.Header),
			Body:   append([]byte(nil), req.Body...),
		})
		if resp == nil || err != nil {
			return nil, err
		}
		return &sharedialect.DispatchResponse{
			Status: resp.Status,
			Header: cloneStringMap(resp.Header),
			Body:   append([]byte(nil), resp.Body...),
		}, nil
	}
}

func apiHostResponseFromShared(resp *sharedialect.Response) *apihostdialect.Response {
	if resp == nil {
		return nil
	}
	var stream *apihostdialect.ResponseStream
	if resp.Stream != nil {
		stream = &apihostdialect.ResponseStream{
			Reader:        resp.Stream.Reader,
			ContentType:   resp.Stream.ContentType,
			ContentLength: resp.Stream.ContentLength,
		}
	}
	return &apihostdialect.Response{
		Status:  resp.Status,
		Body:    resp.Body,
		Headers: cloneStringMap(resp.Headers),
		Stream:  stream,
	}
}

func apiHostErrorFromShared(err error) error {
	var wireErr *sharederrors.DomainWireError
	if !apihostdialect.IsInternal(err) && errors.As(err, &wireErr) {
		return apiHostDomainWireErrorFromShared(wireErr)
	}
	return err
}

func apiHostDomainWireErrorFromShared(err *sharederrors.DomainWireError) *apihostdialect.DomainWireError {
	if err == nil {
		return nil
	}
	return &apihostdialect.DomainWireError{
		Kind:                  apihostdialect.WireConditionKind(err.Kind),
		Message:               err.Message,
		Fields:                append([]string(nil), err.Fields...),
		Cause:                 err.Cause,
		CreateFromSourceGuard: apiHostCreateFromSourceGuardFromShared(err.CreateFromSourceGuard),
	}
}

func sharedDomainWireErrorFromAPIHost(err *apihostdialect.DomainWireError) *sharederrors.DomainWireError {
	if err == nil {
		return nil
	}
	return &sharederrors.DomainWireError{
		Kind:                  sharederrors.WireConditionKind(err.Kind),
		Message:               err.Message,
		Fields:                append([]string(nil), err.Fields...),
		Cause:                 err.Cause,
		CreateFromSourceGuard: sharedCreateFromSourceGuardFromAPIHost(err.CreateFromSourceGuard),
	}
}

func apiHostCreateFromSourceGuardFromShared(details *sharederrors.CreateFromSourceGuardDetails) *apihostdialect.CreateFromSourceGuardDetails {
	if details == nil {
		return nil
	}
	return &apihostdialect.CreateFromSourceGuardDetails{
		ObjectName:  details.ObjectName,
		ActionNames: append([]string(nil), details.ActionNames...),
	}
}

func sharedCreateFromSourceGuardFromAPIHost(details *apihostdialect.CreateFromSourceGuardDetails) *sharederrors.CreateFromSourceGuardDetails {
	if details == nil {
		return nil
	}
	return &sharederrors.CreateFromSourceGuardDetails{
		ObjectName:  details.ObjectName,
		ActionNames: append([]string(nil), details.ActionNames...),
	}
}

func cloneStringMap(in map[string]string) map[string]string {
	if len(in) == 0 {
		return nil
	}
	out := make(map[string]string, len(in))
	for k, v := range in {
		out[k] = v
	}
	return out
}

func cloneStringSliceMap(in map[string][]string) map[string][]string {
	if len(in) == 0 {
		return nil
	}
	out := make(map[string][]string, len(in))
	for k, values := range in {
		out[k] = append([]string(nil), values...)
	}
	return out
}
