package authmanagement

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"

	"github.com/dgrijalva/jwt-go"

	"github.com/porter-dev/porter/internal/auth/token"
	"github.com/porter-dev/porter/internal/models"

	"github.com/porter-dev/porter/internal/telemetry"

	"connectrpc.com/connect"
	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
)

// APIToken returns an encoded token for programmatic access to the Porter UI. Currently, this token is hardcoded
// to use the "porter-agent-token" name. Once this endpoint is used for multiple tokens, the GRPC request should
// include the token name or type as an argument.
func (a AuthManagementService) APIToken(ctx context.Context, req *connect.Request[porterv1.APITokenRequest]) (*connect.Response[porterv1.APITokenResponse], error) {
	ctx, span := telemetry.NewSpan(ctx, "auth-endpoint-api-token")
	defer span.End()

	resp := connect.NewResponse(&porterv1.APITokenResponse{})

	if req == nil {
		err := telemetry.Error(ctx, span, nil, "missing request")
		return resp, connect.NewError(connect.CodeInvalidArgument, err)
	}
	if req.Msg == nil {
		err := telemetry.Error(ctx, span, nil, "missing request message")
		return resp, connect.NewError(connect.CodeInvalidArgument, err)
	}
	if req.Msg.ProjectId == 0 {
		err := telemetry.Error(ctx, span, nil, "missing project id")
		return resp, connect.NewError(connect.CodeInvalidArgument, err)
	}

	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "project-id", Value: req.Msg.ProjectId})

	existingTokens, err := a.Config.APITokenManager.ListAPITokensByProjectID(uint(req.Msg.ProjectId))
	if err != nil {
		return resp, telemetry.Error(ctx, span, err, "error listing api tokens")
	}

	var apiToken *models.APIToken
	for _, tok := range existingTokens {
		if tok.Name == "porter-agent-token" {
			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "token-exists", Value: true})
			apiToken = tok
		}
	}

	if apiToken == nil {
		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "token-exists", Value: false})

		tokenID, err := uuid.NewUUID()
		if err != nil {
			return resp, telemetry.Error(ctx, span, err, "error generating tokenID")
		}

		expiresAt := time.Now().Add(time.Hour * 24 * 365)

		apiToken = &models.APIToken{
			UniqueID:   tokenID.String(),
			ProjectID:  uint(req.Msg.ProjectId),
			Expiry:     &expiresAt,
			Revoked:    false,
			PolicyUID:  "developer",
			PolicyName: "developer",
			Name:       "porter-agent-token",
		}

		apiToken, err = a.Config.APITokenManager.CreateAPIToken(apiToken)
		if err != nil {
			return resp, telemetry.Error(ctx, span, err, "error creating api token")
		}
	}

	telemetry.WithAttributes(span,
		telemetry.AttributeKV{Key: "token-id", Value: apiToken.UniqueID},
		telemetry.AttributeKV{Key: "expiry", Value: apiToken.Expiry.UTC().String()},
	)

	now := time.Now().UTC()

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"sub_kind":   "porter-agent",
		"sub":        string(token.API),
		"iat":        fmt.Sprintf("%d", now.Unix()),
		"project_id": apiToken.ProjectID,
		"token_id":   apiToken.UniqueID,
	})

	encodedToken, err := token.SignedString([]byte(a.Config.TokenGeneratorSecret))
	if err != nil {
		return resp, telemetry.Error(ctx, span, err, "error signing token")
	}

	resp.Msg.Token = encodedToken

	return resp, nil
}
