package api

import (
	"crypto/x509"
	"encoding/json"
	"encoding/pem"
	"git.lumeweb.com/LumeWeb/gitea-github-proxy/config"
	"git.lumeweb.com/LumeWeb/gitea-github-proxy/db/model"
	"github.com/golang-jwt/jwt"
	"github.com/gorilla/mux"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"net/http"
	"strconv"
	"time"
)

type appInstallApi struct {
	config *config.Config
	logger *zap.Logger
	db     *gorm.DB
}

func newAppInstallApi(cfg *config.Config, db *gorm.DB, logger *zap.Logger) *appInstallApi {
	return &appInstallApi{config: cfg, logger: logger, db: db}
}

func (a *appInstallApi) handlerAppGetAccessToken(w http.ResponseWriter, r *http.Request) {

	now := time.Now().Add(-30 * time.Second)
	expirationTime := now.Add(10 * time.Minute)

	appId := mux.Vars(r)["installation_id"]
	appIdInt, err := strconv.ParseInt(appId, 10, 64)

	if err != nil {
		http.Error(w, "Failed to parse app id", http.StatusBadRequest)
		a.logger.Error("Failed to parse app id", zap.Error(err))
		return
	}

	appRecord := &model.Apps{}
	appRecord.ID = uint(appIdInt)

	if err := a.db.First(appRecord).Error; err != nil {
		http.Error(w, "Failed to find app", http.StatusNotFound)
		a.logger.Error("Failed to find app", zap.Error(err))
		return
	}

	block, _ := pem.Decode([]byte(appRecord.PrivateKey))
	if block == nil {
		http.Error(w, "Failed to parse PEM block containing the key", http.StatusInternalServerError)
		a.logger.Error("Failed to parse PEM block containing the key")
		return
	}

	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		http.Error(w, "Failed to parse DER encoded private key", http.StatusInternalServerError)
		a.logger.Error("Failed to parse DER encoded private key", zap.Error(err))
	}

	claims := jwt.MapClaims{
		"iss": appId,
		"iat": now.Unix(),
		"exp": expirationTime,
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

	signedToken, err := token.SignedString(privateKey)
	if err != nil {
		http.Error(w, "Failed to sign token", http.StatusInternalServerError)
		a.logger.Error("Failed to sign token", zap.Error(err))
		return
	}

	out := struct {
		Token     string    `json:"token"`
		ExpiresAt time.Time `json:"expires_at"`
	}{
		Token:     signedToken,
		ExpiresAt: expirationTime,
	}

	a.respond(w, http.StatusCreated, out)

}

func (r appInstallApi) respond(w http.ResponseWriter, status int, data interface{}) {
	jsonData, err := json.Marshal(data)
	if err != nil {
		http.Error(w, "Failed to marshal response", http.StatusInternalServerError)
		r.logger.Error("Failed to marshal response", zap.Error(err))
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	if data != nil {
		_, _ = w.Write(jsonData)
	}
}

func setupAppInstallRoutes(params RouteParams) {
	logger := params.Logger
	cfg := params.Config
	db := params.Db
	r := params.R

	appInstallApi := newAppInstallApi(cfg, db, logger)

	appRouter := r.PathPrefix("/api/v3/app").Subrouter()
	appRouter.Use(githubRestVerifyMiddleware(params.Db))
	appRouter.Use(githubRestRequireAuthMiddleware(params.Config))

	appRouter.HandleFunc("/installations/{installation_id}/access_tokens", appInstallApi.handlerAppGetAccessToken).Methods("POST").Name("app-install-get-access-token")
}