package auth

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"strconv"

	"github.com/gofiber/fiber/v2"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/github"
	"gorm.io/gorm"
	"rul.sh/vaulterm/server/models"
	"rul.sh/vaulterm/server/utils"
)

var githubCfg *oauth2.Config

func getGithubConfig() *oauth2.Config {
	if githubCfg != nil {
		return githubCfg
	}

	githubCfg = &oauth2.Config{
		ClientID:     os.Getenv("GITHUB_CLIENT_ID"),
		ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
		Endpoint:     github.Endpoint,
		RedirectURL:  "vaulterm://auth/login",
		Scopes:       []string{"read:user"},
	}
	return githubCfg
}

func githubCallback(c *fiber.Ctx) error {
	var body struct {
		Code string `json:"code"`
	}
	if err := c.BodyParser(&body); err != nil {
		return c.Status(fiber.StatusBadRequest).SendString("Failed to parse request body")
	}
	if body.Code == "" {
		return c.Status(fiber.StatusBadRequest).SendString("Missing code")
	}

	// Exchange code for a token
	cfg := getGithubConfig()
	token, err := cfg.Exchange(c.Context(), body.Code)
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Failed to exchange token")
	}

	// Retrieve user info
	client := cfg.Client(c.Context(), token)
	resp, err := client.Get("https://api.github.com/user")
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Failed to get user info")
	}
	defer resp.Body.Close()

	data, _ := io.ReadAll(resp.Body)
	if resp.StatusCode != 200 {
		return c.Status(fiber.StatusInternalServerError).
			SendString(fmt.Sprintf("GitHub API error: %s", string(data)))
	}

	// Parse user info
	var user struct {
		Login     string `json:"login"`
		ID        int    `json:"id"`
		Name      string `json:"name"`
		AvatarURL string `json:"avatar_url"`
		Email     string `json:"email"`
	}

	if err := json.Unmarshal(data, &user); err != nil {
		return c.Status(fiber.StatusInternalServerError).SendString("Failed to parse user info")
	}

	repo := NewRepository()
	accountId := strconv.Itoa(user.ID)
	userAccount, err := repo.FindUserAccount("github", accountId)

	// Register the user if the account not yet registered
	if errors.Is(err, gorm.ErrRecordNotFound) {
		acc := models.UserAccount{
			Type:      "github",
			AccountID: accountId,
			Username:  user.Login,
			Email:     user.Email,
		}
		user := models.User{
			Name:     user.Name,
			Role:     models.UserRoleUser,
			Image:    user.AvatarURL,
			Accounts: []*models.UserAccount{&acc},
		}

		sessionId, err := repo.CreateUser(&user)
		if err != nil {
			return utils.ResponseError(c, err, 500)
		}

		return c.JSON(fiber.Map{
			"user":      user,
			"sessionId": sessionId,
		})
	}

	if err != nil {
		return utils.ResponseError(c, err, 500)
	}
	sessionId, err := repo.CreateUserSession(&userAccount.User)
	if err != nil {
		return utils.ResponseError(c, err, 500)
	}

	return c.JSON(fiber.Map{
		"user":      userAccount.User,
		"sessionId": sessionId,
	})
}