package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strconv"
)

type AirbusAPI struct {
	BaseURL            string
	Token              string
	SessionID          int64
	InsecureSkipVerify bool
}

func (a *AirbusAPI) request(method, endpoint string, data io.Reader, out interface{}) error {
	var req *http.Request
	var err error

	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: a.InsecureSkipVerify},
		},
	}

	if data != nil {
		req, err = http.NewRequest(method, a.BaseURL+endpoint, data)
	} else {
		req, err = http.NewRequest(method, a.BaseURL+endpoint, nil)
	}
	if err != nil {
		return fmt.Errorf("unable to prepare request to %q: %w", endpoint, err)
	}

	req.Header.Add("Authorization", "Bearer "+a.Token)
	req.Header.Add("Accept", "application/json")
	req.Header.Add("Content-Type", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("error during request execution to %q: %w", endpoint, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		if out != nil {
			jdec := json.NewDecoder(resp.Body)

			if err := jdec.Decode(out); err != nil {
				return fmt.Errorf("an error occurs when trying to decode response: %w", err)
			}
		}
	} else if all, err := io.ReadAll(resp.Body); err != nil {
		return fmt.Errorf("error returned by the API + error on decoding: %d // %w", resp.StatusCode, err)
	} else {
		return fmt.Errorf("error returned by the API: %d -> %s", resp.StatusCode, all)
	}

	return nil
}

type AirbusUserId int64

func NewAirbusUserId(externalid string) AirbusUserId {
	v, _ := strconv.ParseInt(externalid, 10, 64)
	return AirbusUserId(v)
}

func (aui AirbusUserId) String() string {
	return strconv.FormatInt(int64(aui), 10)
}

type AirbusUserData struct {
	Data []AirbusUser `json:"data"`
}

type AirbusUser struct {
	Id       AirbusUserId `json:"id"`
	Email    string       `json:"email"`
	Name     string       `json:"name"`
	Nickname string       `json:"nickname"`
}

func (a *AirbusAPI) GetUsers() (users AirbusUserData, err error) {
	err = a.request("GET", fmt.Sprintf("/sessions/%d/users", a.SessionID), nil, &users)
	return
}

func (a *AirbusAPI) GetUserFromName(name string) (*AirbusUser, error) {
	users, err := a.GetUsers()
	if err != nil {
		return nil, fmt.Errorf("unable to retrieve users list: %w", err)
	}

	for _, u := range users.Data {
		if u.Name == name {
			return &u, nil
		}
	}

	return nil, fmt.Errorf("unable to find user %q", name)
}

func (a *AirbusAPI) GetUserFromEmail(email string) (*AirbusUser, error) {
	users, err := a.GetUsers()
	if err != nil {
		return nil, fmt.Errorf("unable to retrieve users list: %w", err)
	}

	for _, u := range users.Data {
		if u.Email == email {
			return &u, nil
		}
	}

	return nil, fmt.Errorf("unable to find user with email %q", email)
}

type AirbusChallengeId int64

func (aci AirbusChallengeId) String() string {
	return strconv.FormatInt(int64(aci), 10)
}

type AirbusChallengeData struct {
	Data []AirbusChallenge `json:"data"`
}

type AirbusChallenge struct {
	Id   AirbusChallengeId `json:"id"`
	Name string            `json:"name"`
}

func (a *AirbusAPI) GetChallenges() (challenges AirbusChallengeData, err error) {
	err = a.request("GET", fmt.Sprintf("/v1/sessions/%d/challenges", a.SessionID), nil, &challenges)
	return
}

func (a *AirbusAPI) GetChallengeFromName(name string) (*AirbusChallenge, error) {
	challenges, err := a.GetChallenges()
	if err != nil {
		return nil, fmt.Errorf("unable to retrieve challenges list: %w", err)
	}

	for _, c := range challenges.Data {
		if c.Name == name {
			return &c, nil
		}
	}

	return nil, fmt.Errorf("unable to find challenge %q", name)
}

func (a *AirbusAPI) ValidateChallengeFromUser(team *AirbusTeam, challengeId AirbusChallengeId) (err error) {
	log.Printf("ValidateChallenge: %d, %s, %d", a.SessionID, challengeId.String(), team.Members[0].ID)
	if dryRun {
		return
	}

	err = a.request("GET", fmt.Sprintf("/v1/sessions/%d/%s/%d/validate", a.SessionID, challengeId.String(), team.Members[0].ID), nil, nil)
	return
}

type AirbusUserAwards struct {
	UserId  int64  `json:"gaming_user_id"`
	Message string `json:"name"`
	Value   int64  `json:"value"`
}

func (a *AirbusAPI) AwardUser(team *AirbusTeam, value int64, message string) (err error) {
	awards := AirbusUserAwards{
		UserId:  team.Members[0].ID,
		Message: message,
		Value:   value,
	}

	var marshalled []byte
	marshalled, err = json.Marshal(awards)
	if err != nil {
		return
	}

	log.Printf("AwardUser: %s", marshalled)
	if dryRun {
		return
	}

	err = a.request("POST", fmt.Sprintf("/v1/sessions/%d/awards", a.SessionID), bytes.NewReader(marshalled), nil)
	if err != nil {
		return err
	}

	return
}
