package oauth

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"

	"gitea.local/admin/hspguard/internal/config"
	"gitea.local/admin/hspguard/internal/repository"
	"gitea.local/admin/hspguard/internal/types"
	"gitea.local/admin/hspguard/internal/util"
	"gitea.local/admin/hspguard/internal/web"
	"github.com/go-chi/chi/v5"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
)

type OAuthHandler struct {
	repo *repository.Queries
	cfg  *config.AppConfig
}

func NewOAuthHandler(repo *repository.Queries, cfg *config.AppConfig) *OAuthHandler {
	return &OAuthHandler{
		repo,
		cfg,
	}
}

func (h *OAuthHandler) RegisterRoutes(router chi.Router) {
	router.Route("/oauth", func(r chi.Router) {
		r.Post("/token", h.tokenEndpoint)

		r.Post("/code", h.getAuthCode)
	})
}

func (h *OAuthHandler) WriteJWKS(w http.ResponseWriter, r *http.Request) {
	pubKey, err := util.ParseBase64PublicKey(h.cfg.Jwt.PublicKey)
	if err != nil {
		web.Error(w, "failed to parse public key", http.StatusInternalServerError)
	}

	n := base64.RawURLEncoding.EncodeToString(pubKey.N.Bytes())
	e := base64.RawURLEncoding.EncodeToString([]byte{1, 0, 1}) // 65537 = 0x010001

	jwks := map[string]interface{}{
		"keys": []map[string]string{
			{
				"kty": "RSA",
				"kid": "my-rsa-key-1",
				"use": "sig",
				"alg": "RS256",
				"n":   n,
				"e":   e,
			},
		},
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(jwks)
}

func (h *OAuthHandler) OpenIdConfiguration(w http.ResponseWriter, r *http.Request) {
	type Response struct {
		TokenEndpoint         string `json:"token_endpoint"`
		AuthorizationEndpoint string `json:"authorization_endpoint"`
		JwksURI               string `json:"jwks_uri"`
		Issuer                string `json:"issuer"`
		EndSessionEndpoint    string `json:"end_session_endpoint"`
	}

	w.Header().Set("Content-Type", "application/json")

	encoder := json.NewEncoder(w)
	if err := encoder.Encode(Response{
		TokenEndpoint:         h.cfg.Jwt.Issuer + "/api/v1/oauth/token",
		AuthorizationEndpoint: h.cfg.Jwt.Issuer + "/authorize",
		JwksURI:               h.cfg.Jwt.Issuer + "/.well-known/jwks.json",
		Issuer:                h.cfg.Jwt.Issuer,
		EndSessionEndpoint:    h.cfg.Jwt.Issuer + "/api/v1/oauth/logout",
	}); err != nil {
		web.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

func (h *OAuthHandler) getAuthCode(w http.ResponseWriter, r *http.Request) {
	userId, ok := util.GetRequestUserId(r.Context())
	if !ok {
		web.Error(w, "failed to get user id from auth session", http.StatusInternalServerError)
		return
	}

	user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(userId))
	if err != nil {
		web.Error(w, "user with provided id does not exist", http.StatusUnauthorized)
		return
	}

	type Request struct {
		Nonce string `json:"nonce"`
	}

	var req Request

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&req); err != nil {
		web.Error(w, "nonce field is required in request", http.StatusBadRequest)
		return
	}

	// TODO: Create real authorization code

	type Response struct {
		Code string `json:"code"`
	}

	encoder := json.NewEncoder(w)

	w.Header().Set("Content-Type", "application/json")

	if err := encoder.Encode(Response{
		Code: fmt.Sprintf("%s,%s", user.ID.String(), req.Nonce),
	}); err != nil {
		web.Error(w, "failed to encode response", http.StatusInternalServerError)
	}
}

func (h *OAuthHandler) tokenEndpoint(w http.ResponseWriter, r *http.Request) {
	log.Println("[OAUTH] New request to token endpoint")

	authHeader := r.Header.Get("Authorization")
	if authHeader == "" || !strings.HasPrefix(authHeader, "Basic ") {
		w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
		return
	}

	// Decode credentials
	payload, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(authHeader, "Basic "))
	if err != nil {
		http.Error(w, "Invalid auth encoding", http.StatusBadRequest)
		return
	}

	var clientId string
	var clientSecret string

	parts := strings.SplitN(string(payload), ":", 2)
	if len(parts) != 2 {
		http.Error(w, "Unauthorized", http.StatusForbidden)
		return
	}

	clientId = parts[0]
	clientSecret = parts[1]

	log.Printf("Some client is trying to exchange code with id: %s and secret: %s\n", clientId, clientSecret)

	// Parse the form data
	err = r.ParseForm()
	if err != nil {
		http.Error(w, "Failed to parse form", http.StatusBadRequest)
		return
	}

	grantType := r.FormValue("grant_type")
	redirectUri := r.FormValue("redirect_uri")

	log.Printf("Redirect URI is %s\n", redirectUri)

	switch grantType {
	case "authorization_code":
		code := r.FormValue("code")

		fmt.Printf("Code received: %s\n", code)

		// TODO: Verify code from another db table
		nonce := strings.Split(code, ",")[1]

		userId := strings.Split(code, ",")[0]

		user, err := h.repo.FindUserId(r.Context(), uuid.MustParse(userId))
		if err != nil {
			web.Error(w, "requested user not found", http.StatusNotFound)
			return
		}

		claims := types.ApiClaims{
			Email: user.Email,
			// TODO:
			EmailVerified: true,
			Name:          user.FullName,
			Picture:       user.ProfilePicture.String,
			Nonce:         nonce,
			Roles:         []string{"user", "admin"},
			RegisteredClaims: jwt.RegisteredClaims{
				Issuer: h.cfg.Jwt.Issuer,
				// TODO: use dedicated API id that is in local DB and bind to user there
				Subject:   user.ID.String(),
				Audience:  jwt.ClaimStrings{clientId},
				IssuedAt:  jwt.NewNumericDate(time.Now()),
				ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
			},
		}

		idToken, err := util.SignJwtToken(claims, h.cfg.Jwt.PrivateKey)
		if err != nil {
			web.Error(w, "failed to sign id token", http.StatusInternalServerError)
			return
		}

		type Response struct {
			IdToken     string `json:"id_token"`
			TokenType   string `json:"token_type"`
			AccessToken string `json:"access_token"`
			Email       string `json:"email"`
			// TODO: add expires_in, refresh_token, scope (RFC 8693 $2)
		}

		response := Response{
			IdToken:   idToken,
			TokenType: "Bearer",
			// FIXME:
			AccessToken: idToken,
			Email:       user.Email,
		}

		log.Printf("sending following response: %#v\n", response)

		w.Header().Set("Content-Type", "application/json")
		encoder := json.NewEncoder(w)
		if err := encoder.Encode(response); err != nil {
			web.Error(w, "failed to encode response", http.StatusInternalServerError)
		}
	default:
		web.Error(w, "unsupported grant type", http.StatusBadRequest)
	}
}

// func (h *OAuthHandler) Metadata(w http.ResponseWriter, r *http.Request) {
// 	type Response struct {
// 		TokenEndpoint string `json:"token_endpoint"`
// 		AuthEndpoint  string `json:"authorization_endpoint"`
// 		Issuer        string `json:"issuer"`
// 		JwksUri       string `json:"jwks_uri"`
// 	}

// 	encoder := json.NewEncoder(w)

// 	if err := encoder.Encode(Response{
// 		TokenEndpoint: "http://192.168.178.21:3001/api/v1/oauth/token",
// 		AuthEndpoint:  "https://147f-2a00-10-5b00-c801-4882-7ef0-5e68-d360.ngrok-free.app/authorize",
// 		JwksUri:       "http://192.168.178.21:3001/.well-known/jwks.json",
// 		Issuer:        "http://192.168.178.21:3001",
// 	}); err != nil {
// 		web.Error(w, "failed to encode response", http.StatusInternalServerError)
// 	}
// }
