package auth

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/lestrrat-go/jwx/jwa"
	"github.com/lestrrat-go/jwx/jwt"
	log "github.com/sirupsen/logrus"
	"reichard.io/imagini/graph/model"
)

func (auth *AuthManager) ValidateJWTRefreshToken(refreshJWT string) (jwt.Token, error) {
	byteRefreshJWT := []byte(refreshJWT)

	// Acquire Relevant Device
	unverifiedToken, err := jwt.ParseBytes(byteRefreshJWT)
	did, ok := unverifiedToken.Get("did")
	if !ok {
		return nil, errors.New("did does not exist")
	}
	deviceID, err := uuid.Parse(fmt.Sprintf("%v", did))
	if err != nil {
		return nil, errors.New("did does not parse")
	}
	device := &model.Device{ID: deviceID.String()}
	_, err = auth.DB.Device(device)
	if err != nil {
		return nil, err
	}

	// Verify & Validate Token
	verifiedToken, err := jwt.ParseBytes(byteRefreshJWT,
		jwt.WithValidate(true),
		jwt.WithVerify(jwa.HS256, []byte(*device.RefreshKey)),
	)
	if err != nil {
		fmt.Println("failed to parse payload: ", err)
		return nil, err
	}
	return verifiedToken, nil
}

func (auth *AuthManager) ValidateJWTAccessToken(accessJWT string) (jwt.Token, error) {
	byteAccessJWT := []byte(accessJWT)
	verifiedToken, err := jwt.ParseBytes(byteAccessJWT,
		jwt.WithValidate(true),
		jwt.WithVerify(jwa.HS256, []byte(auth.Config.JWTSecret)),
	)

	if err != nil {
		return nil, err
	}

	return verifiedToken, nil
}

func (auth *AuthManager) CreateJWTRefreshToken(user model.User, device model.Device) (string, error) {
	// Acquire Refresh Key
	byteKey := []byte(*device.RefreshKey)

	// Create New Token
	tm := time.Now()
	t := jwt.New()
	t.Set(`did`, device.ID)           // Device ID
	t.Set(jwt.SubjectKey, user.ID)    // User ID
	t.Set(jwt.AudienceKey, `imagini`) // App ID
	t.Set(jwt.IssuedAtKey, tm)        // Issued At

	// iOS & Android = Never Expiring Refresh Token
	if device.Type != "iOS" && device.Type != "Android" {
		t.Set(jwt.ExpirationKey, tm.Add(time.Hour*24)) // 1 Day Access Key
	}

	// Validate Token Creation
	_, err := json.MarshalIndent(t, "", "  ")
	if err != nil {
		fmt.Printf("failed to generate JSON: %s\n", err)
		return "", err
	}

	// Sign Token
	signed, err := jwt.Sign(t, jwa.HS256, byteKey)
	if err != nil {
		log.Printf("failed to sign token: %s", err)
		return "", err
	}

	// Return Token
	return string(signed), nil
}

func (auth *AuthManager) CreateJWTAccessToken(user model.User, device model.Device) (string, error) {
	// Create New Token
	tm := time.Now()
	t := jwt.New()
	t.Set(`did`, device.ID)                       // Device ID
	t.Set(`role`, user.Role.String())             // User Role (Admin / User)
	t.Set(jwt.SubjectKey, user.ID)                // User ID
	t.Set(jwt.AudienceKey, `imagini`)             // App ID
	t.Set(jwt.IssuedAtKey, tm)                    // Issued At
	t.Set(jwt.ExpirationKey, tm.Add(time.Hour*2)) // 2 Hour Access Key

	// Validate Token Creation
	_, err := json.MarshalIndent(t, "", "  ")
	if err != nil {
		fmt.Printf("failed to generate JSON: %s\n", err)
		return "", err
	}

	// Use Server Key
	byteKey := []byte(auth.Config.JWTSecret)

	// Sign Token
	signed, err := jwt.Sign(t, jwa.HS256, byteKey)
	if err != nil {
		log.Printf("failed to sign token: %s", err)
		return "", err
	}

	// Return Token
	return string(signed), nil
}
