package mapping

import (
	"context"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/sessionv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
)

func ToWireCreateSessionRequest(ctx context.Context, request semantic.CreateRequest) (*runtimev1.CreateSessionRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCreateRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.CreateSessionRequest{
		AppInstallationId:   request.AppInstallationID,
		Method:              sessionAuthenticationMethodToWire(request.Method),
		ApplicationClientId: request.ApplicationClientID,
		RequestedScope:      request.RequestedScope,
		FederatedProviderId: request.FederatedProviderID,
		SourceAddress:       request.SourceAddress,
		UserAgent:           request.UserAgent,
		IssueControlProof:   request.IssueControlProof,
	}, nil
}

func FromWireCreateSessionRequest(ctx context.Context, request *runtimev1.CreateSessionRequest) (semantic.CreateRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CreateRequest{}, err
	}
	if request == nil {
		return semantic.CreateRequest{}, invalidSessionWireRequest()
	}
	result := semantic.CreateRequest{
		AppInstallationID:   request.GetAppInstallationId(),
		Method:              sessionAuthenticationMethodFromWire(request.GetMethod()),
		ApplicationClientID: request.GetApplicationClientId(),
		RequestedScope:      request.GetRequestedScope(),
		FederatedProviderID: request.GetFederatedProviderId(),
		SourceAddress:       request.GetSourceAddress(),
		UserAgent:           request.GetUserAgent(),
		IssueControlProof:   request.GetIssueControlProof(),
	}
	if err := semantic.ValidateCreateRequest(ctx, result); err != nil {
		return semantic.CreateRequest{}, err
	}
	return result, nil
}

func ToWireCreateSessionResponse(request semantic.CreateRequest, response semantic.CreateResponse) (*runtimev1.CreateSessionResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCreateResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.CreateSessionResponse{
		Session:            sessionToWire(response.Session),
		AccessTokenIssued:  response.AccessTokenIssued,
		ControlProofIssued: response.ControlProofIssued,
	}, nil
}

func FromWireCreateSessionResponse(request semantic.CreateRequest, response *runtimev1.CreateSessionResponse) (semantic.CreateResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CreateResponse{}, err
	}
	if response == nil {
		return semantic.CreateResponse{}, internalSessionWireResponse()
	}
	result := semantic.CreateResponse{
		Session:            sessionFromWire(response.GetSession()),
		AccessTokenIssued:  response.GetAccessTokenIssued(),
		ControlProofIssued: response.GetControlProofIssued(),
	}
	if err := semantic.ValidateCreateResponse(request, result); err != nil {
		return semantic.CreateResponse{}, err
	}
	return result, nil
}

func ToWireReadSessionRequest(ctx context.Context, request semantic.ReadRequest) (*runtimev1.ReadSessionRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateReadRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ReadSessionRequest{
		DocumentPolicy:    documentPolicyRequestToWire(request.DocumentPolicy),
		AppInstallationId: request.AppInstallationID,
		AllowRenewal:      request.AllowRenewal,
		UseControlProof:   request.UseControlProof,
	}, nil
}

func FromWireReadSessionRequest(ctx context.Context, request *runtimev1.ReadSessionRequest) (semantic.ReadRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ReadRequest{}, err
	}
	if request == nil {
		return semantic.ReadRequest{}, invalidSessionWireRequest()
	}
	result := semantic.ReadRequest{
		DocumentPolicy:    documentPolicyRequestFromWire(request.GetDocumentPolicy()),
		AppInstallationID: request.GetAppInstallationId(),
		AllowRenewal:      request.GetAllowRenewal(),
		UseControlProof:   request.GetUseControlProof(),
	}
	if err := semantic.ValidateReadRequest(ctx, result); err != nil {
		return semantic.ReadRequest{}, err
	}
	return result, nil
}

func ToWireReadSessionResponse(request semantic.ReadRequest, response semantic.ReadResponse) (*runtimev1.ReadSessionResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateReadResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.ReadSessionResponse{
		DocumentPolicy:                  documentPolicyToWire(response.DocumentPolicy),
		Session:                         sessionToWire(response.Session),
		ReplacementAccessTokenIssued:    response.ReplacementAccessTokenIssued,
		ControlProofExpiresAtUnixSecond: response.ControlProofExpiresAtUnixSecond,
	}, nil
}

func FromWireReadSessionResponse(request semantic.ReadRequest, response *runtimev1.ReadSessionResponse) (semantic.ReadResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ReadResponse{}, err
	}
	if response == nil {
		return semantic.ReadResponse{}, internalSessionWireResponse()
	}
	result := semantic.ReadResponse{
		DocumentPolicy:                  documentPolicyFromWire(response.GetDocumentPolicy()),
		Session:                         sessionFromWire(response.GetSession()),
		ReplacementAccessTokenIssued:    response.GetReplacementAccessTokenIssued(),
		ControlProofExpiresAtUnixSecond: response.GetControlProofExpiresAtUnixSecond(),
	}
	if err := semantic.ValidateReadResponse(request, result); err != nil {
		return semantic.ReadResponse{}, err
	}
	return result, nil
}

func ToWireRevokeSessionRequest(ctx context.Context, request semantic.RevokeRequest) (*runtimev1.RevokeSessionRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateRevokeRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.RevokeSessionRequest{
		AppInstallationId: request.AppInstallationID,
		UseControlProof:   request.UseControlProof,
	}, nil
}

func FromWireRevokeSessionRequest(ctx context.Context, request *runtimev1.RevokeSessionRequest) (semantic.RevokeRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.RevokeRequest{}, err
	}
	if request == nil {
		return semantic.RevokeRequest{}, invalidSessionWireRequest()
	}
	result := semantic.RevokeRequest{
		AppInstallationID: request.GetAppInstallationId(),
		UseControlProof:   request.GetUseControlProof(),
	}
	if err := semantic.ValidateRevokeRequest(ctx, result); err != nil {
		return semantic.RevokeRequest{}, err
	}
	return result, nil
}

func ToWireRevokeSessionResponse(response semantic.RevokeResponse) (*runtimev1.RevokeSessionResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateRevokeResponse(response); err != nil {
		return nil, err
	}
	return &runtimev1.RevokeSessionResponse{}, nil
}

func FromWireRevokeSessionResponse(response *runtimev1.RevokeSessionResponse) (semantic.RevokeResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.RevokeResponse{}, err
	}
	if response == nil {
		return semantic.RevokeResponse{}, internalSessionWireResponse()
	}
	return semantic.RevokeResponse{}, nil
}

func sessionAuthenticationMethodToWire(method semantic.AuthenticationMethod) runtimev1.SessionAuthenticationMethod {
	switch method {
	case semantic.AuthenticationMethodPassword:
		return runtimev1.SessionAuthenticationMethod_SESSION_AUTHENTICATION_METHOD_PASSWORD
	case semantic.AuthenticationMethodFederatedIDToken:
		return runtimev1.SessionAuthenticationMethod_SESSION_AUTHENTICATION_METHOD_FEDERATED_ID_TOKEN
	default:
		return runtimev1.SessionAuthenticationMethod_SESSION_AUTHENTICATION_METHOD_UNSPECIFIED
	}
}

func sessionAuthenticationMethodFromWire(method runtimev1.SessionAuthenticationMethod) semantic.AuthenticationMethod {
	switch method {
	case runtimev1.SessionAuthenticationMethod_SESSION_AUTHENTICATION_METHOD_PASSWORD:
		return semantic.AuthenticationMethodPassword
	case runtimev1.SessionAuthenticationMethod_SESSION_AUTHENTICATION_METHOD_FEDERATED_ID_TOKEN:
		return semantic.AuthenticationMethodFederatedIDToken
	default:
		return ""
	}
}

func sessionToWire(session semantic.Session) *runtimev1.SessionView {
	return &runtimev1.SessionView{
		Principal: &runtimev1.SessionPrincipal{
			PrincipalId: session.Principal.PrincipalID,
			DisplayName: session.Principal.DisplayName,
			Email:       session.Principal.Email,
			ProfileId:   session.Principal.ProfileID,
			RoleId:      session.Principal.RoleID,
		},
		ExpiresAtUnixSecond: session.ExpiresAtUnixSecond,
	}
}

func sessionFromWire(session *runtimev1.SessionView) semantic.Session {
	if session == nil || session.GetPrincipal() == nil {
		return semantic.Session{}
	}
	principal := session.GetPrincipal()
	return semantic.Session{
		Principal: semantic.Principal{
			PrincipalID: principal.GetPrincipalId(),
			DisplayName: principal.GetDisplayName(),
			Email:       principal.GetEmail(),
			ProfileID:   principal.GetProfileId(),
			RoleID:      principal.GetRoleId(),
		},
		ExpiresAtUnixSecond: session.GetExpiresAtUnixSecond(),
	}
}

func invalidSessionWireRequest() error {
	return &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentResponse}
}

func internalSessionWireResponse() error {
	return &semantic.Failure{Category: semantic.FailureInternal}
}

func documentPolicyRequestToWire(p *semantic.DocumentPolicyRequest) *runtimev1.SessionDocumentPolicyRequest {
	if p == nil {
		return nil
	}
	out := &runtimev1.SessionDocumentPolicyRequest{RouteFamilyPrefix: p.RouteFamilyPrefix}
	for _, r := range p.LinkRoutes {
		out.LinkRoutes = append(out.LinkRoutes, &runtimev1.SessionDocumentLinkRoute{Link: r.Link, Method: r.Method, Path: r.Path})
	}
	return out
}
func documentPolicyRequestFromWire(p *runtimev1.SessionDocumentPolicyRequest) *semantic.DocumentPolicyRequest {
	if p == nil {
		return nil
	}
	out := &semantic.DocumentPolicyRequest{RouteFamilyPrefix: p.GetRouteFamilyPrefix()}
	for _, r := range p.GetLinkRoutes() {
		out.LinkRoutes = append(out.LinkRoutes, semantic.DocumentLinkRoute{Link: r.GetLink(), Method: r.GetMethod(), Path: r.GetPath()})
	}
	return out
}
func documentPolicyToWire(p *semantic.DocumentPolicy) *runtimev1.SessionDocumentPolicy {
	if p == nil {
		return nil
	}
	return &runtimev1.SessionDocumentPolicy{Restricted: p.Restricted, AllowedLinks: append([]string(nil), p.AllowedLinks...), HourlyLimit: p.HourlyLimit, DailyLimit: p.DailyLimit}
}
func documentPolicyFromWire(p *runtimev1.SessionDocumentPolicy) *semantic.DocumentPolicy {
	if p == nil {
		return nil
	}
	return &semantic.DocumentPolicy{Restricted: p.GetRestricted(), AllowedLinks: append([]string(nil), p.GetAllowedLinks()...), HourlyLimit: p.GetHourlyLimit(), DailyLimit: p.GetDailyLimit()}
}
