package internal_key

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/dchest/uniuri"
	"github.com/gin-gonic/gin"
	"github.com/pingcap/log"
	"go.uber.org/fx"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/api/common"
	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/auth/api_key"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/auth/utils"
	"github.com/pingcap-inc/clinic/pkg/db/model"
)

type InternalKeyService struct{}

func NewInternalKeyService() *InternalKeyService {
	return &InternalKeyService{}
}

type CreateKeyRequest struct {
	Name      string `json:"name"`
	ExpiresIn *int64 `json:"expires_in"` // Optional expiration in seconds
}

type KeyResponse struct {
	ID        uint   `json:"id"`
	Name      string `json:"name"`
	Key       string `json:"key,omitempty"` // Only returned on creation
	Status    string `json:"status"`
	ExpiresAt *int64 `json:"expires_at,omitempty"`
	CreatedAt int64  `json:"created_at"`
}

type ListKeysResponse struct {
	Total int64         `json:"total"`
	Items []KeyResponse `json:"items"`
}

// Create creates a new internal API key for the current internal user.
// @Summary		Create internal API key
// @Tags			Internal Key
// @Description	Create an API key for internal users (internal=true only)
// @Accept			json
// @Param			x-csrf-token	header		string				true	"get value from login.ValidationResp response"
// @Param			body			body		CreateKeyRequest	false	"optional key name and expiration"
// @Success		200				{object}	KeyResponse
// @Router			/internal/keys [post]
func (s *InternalKeyService) Create(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	if !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("only internal users can create internal API keys"))
		return
	}

	var req CreateKeyRequest
	// Bind JSON is optional, request body may be empty
	// Use ShouldBindJSON to avoid writing 400 on empty body
	_ = c.ShouldBindJSON(&req)

	// Generate the API key
	jsonKey := api_key.ApiKeyJson{
		OrgId:  0, // OrgId=0 indicates internal user credential
		UserId: u.UserID,
		Key:    uniuri.NewLen(32), // 32 character random key
	}

	// Hash the key for storage
	hashedKey, err := utils.EncodePassword(jsonKey.Key, fmt.Sprintf("api_key%d", u.UserID))
	if err != nil {
		log.Error("failed to hash key", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("failed to generate API key"))
		return
	}

	// Calculate expiration time if specified
	var expiresAt *time.Time
	if req.ExpiresIn != nil && *req.ExpiresIn > 0 {
		t := time.Now().Add(time.Duration(*req.ExpiresIn) * time.Second)
		expiresAt = &t
	}

	// Create the credential
	cred, err := model.AddInternalAPICredential(u.UserID, req.Name, hashedKey, expiresAt)
	if err != nil {
		log.Error("failed to create internal credential", zap.Uint("user", u.UserID), zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("failed to create API key"))
		return
	}

	// Encode the key for the response
	jsonString, err := json.Marshal(jsonKey)
	if err != nil {
		log.Error("failed to marshal key", zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("failed to generate API key"))
		return
	}

	resp := KeyResponse{
		ID:        cred.ID,
		Name:      cred.Name,
		Key:       base64.StdEncoding.EncodeToString(jsonString),
		Status:    cred.Status,
		CreatedAt: cred.CreatedAt.Unix(),
	}

	if expiresAt != nil {
		expiresAtUnix := expiresAt.Unix()
		resp.ExpiresAt = &expiresAtUnix
	}

	c.JSON(http.StatusOK, resp)
}

// List lists all internal API keys for the current internal user.
// @Summary		List internal API keys
// @Tags			Internal Key
// @Description	List all API keys for the current internal user
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			limit			query		int		false	"the count per page"
// @Param			page			query		int		false	"the page index"
// @Success		200				{object}	ListKeysResponse
// @Router			/internal/keys [get]
func (s *InternalKeyService) List(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	if !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("only internal users can list internal API keys"))
		return
	}

	pq := common.ParsePaginationQuery(c.Request.URL.RequestURI())
	credentials, total, err := model.ListInternalAPICredentials(u.UserID, pq.Page, pq.Limit)
	if err != nil {
		log.Error("failed to list internal credentials", zap.Uint("user", u.UserID), zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("failed to list API keys"))
		return
	}

	keys := make([]KeyResponse, len(credentials))
	for i, cred := range credentials {
		keys[i] = KeyResponse{
			ID:        cred.ID,
			Name:      cred.Name,
			Key:       "******", // Don't expose the key
			Status:    cred.Status,
			CreatedAt: cred.CreatedAt.Unix(),
		}
		if cred.ExpiresAt != nil {
			expiresAtUnix := cred.ExpiresAt.Unix()
			keys[i].ExpiresAt = &expiresAtUnix
		}
	}

	c.JSON(http.StatusOK, ListKeysResponse{
		Total: total,
		Items: keys,
	})
}

// Delete deletes an internal API key.
// @Summary		Delete internal API key
// @Tags			Internal Key
// @Description	Delete an API key for the current internal user
// @Accept			json
// @Param			x-csrf-token	header		string	true	"get value from login.ValidationResp response"
// @Param			id				path		int		true	"key id"
// @Success		200				{object}	common.SuccessResp
// @Router			/internal/keys/{id} [delete]
func (s *InternalKeyService) Delete(c *gin.Context) {
	u := session.GetUser(c)
	if u == nil {
		_ = c.Error(error2.ErrNotFound.New("the user is not found"))
		return
	}

	if !u.Internal {
		_ = c.Error(error2.ErrForbidden.New("only internal users can delete internal API keys"))
		return
	}

	id, err := strconv.ParseUint(c.Param("id"), 10, 64)
	if err != nil {
		_ = c.Error(error2.ErrBadRequest.New("invalid id"))
		return
	}

	// Verify the credential belongs to the current user
	cred, err := model.GetInternalAPICredential(uint(id))
	if err != nil {
		_ = c.Error(error2.ErrNotFound.New("API key not found"))
		return
	}

	if cred.UserID != u.UserID {
		_ = c.Error(error2.ErrForbidden.New("permission denied"))
		return
	}

	if err := model.DeleteInternalAPICredential(uint(id)); err != nil {
		log.Error("failed to delete internal credential", zap.Uint("id", uint(id)), zap.Error(err))
		_ = c.Error(error2.ErrInternal.New("failed to delete API key"))
		return
	}

	c.JSON(http.StatusOK, common.SuccessResponse())
}

func registerRouter(r *gin.RouterGroup, s *InternalKeyService) {
	internal := r.Group("/internal")
	internal.POST("/keys", s.Create)
	internal.GET("/keys", s.List)
	internal.DELETE("/keys/:id", s.Delete)
}

var Module = fx.Options(
	fx.Provide(NewInternalKeyService),
	fx.Invoke(registerRouter),
)
