package remote

import (
	"bytes"
	"regexp"
	"strings"

	"github.com/apex/log"
	"github.com/goccy/go-json"

	"github.com/Tech-Gamer/nwy-wings/parser"
)

const (
	SftpAuthPassword  = SftpAuthRequestType("password")
	SftpAuthPublicKey = SftpAuthRequestType("public_key")
)

// A generic type allowing for easy binding use when making requests to API
// endpoints that only expect a singular argument or something that would not
// benefit from being a typed struct.
//
// Inspired by gin.H, same concept.
type d map[string]interface{}

// Same concept as d, but a map of strings, used for querying GET requests.
type q map[string]string

type ClientOption func(c *client)

type Pagination struct {
	CurrentPage uint `json:"current_page"`
	From        uint `json:"from"`
	LastPage    uint `json:"last_page"`
	PerPage     uint `json:"per_page"`
	To          uint `json:"to"`
	Total       uint `json:"total"`
}

// ServerConfigurationResponse holds the server configuration data returned from
// the Panel. When a server process is started, Wings communicates with the
// Panel to fetch the latest build information as well as get all the details
// needed to parse the given Egg.
//
// This means we do not need to hit Wings each time part of the server is
// updated, and the Panel serves as the source of truth at all times. This also
// means if a configuration is accidentally wiped on Wings we can self-recover
// without too much hassle, so long as Wings is aware of what servers should
// exist on it.
type ServerConfigurationResponse struct {
	Settings             json.RawMessage       `json:"settings"`
	ProcessConfiguration *ProcessConfiguration `json:"process_configuration"`
}

// InstallationScript defines installation script information for a server
// process. This is used when a server is installed for the first time, and when
// a server is marked for re-installation.
type InstallationScript struct {
	ContainerImage string `json:"container_image"`
	Entrypoint     string `json:"entrypoint"`
	Script         string `json:"script"`
}

// RawServerData is a raw response from the API for a server.
type RawServerData struct {
	Uuid                 string          `json:"uuid"`
	Settings             json.RawMessage `json:"settings"`
	ProcessConfiguration json.RawMessage `json:"process_configuration"`
}

type SftpAuthRequestType string

// SftpAuthRequest defines the request details that are passed along to the Panel
// when determining if the credentials provided to Wings are valid.
type SftpAuthRequest struct {
	Type          SftpAuthRequestType `json:"type"`
	User          string              `json:"username"`
	Pass          string              `json:"password"`
	IP            string              `json:"ip"`
	SessionID     []byte              `json:"session_id"`
	ClientVersion []byte              `json:"client_version"`
}

// SftpAuthResponse is returned by the Panel when a pair of SFTP credentials
// is successfully validated. This will include the specific server that was
// matched as well as the permissions that are assigned to the authenticated
// user for the SFTP subsystem.
type SftpAuthResponse struct {
	Server      string   `json:"server"`
	User        string   `json:"user"`
	Permissions []string `json:"permissions"`
}

type OutputLineMatcher struct {
	// raw string to match against. This may or may not be prefixed with
	// `regex:` which indicates we want to match against the regex expression.
	raw []byte
	reg *regexp.Regexp
}

// Matches determines if the provided byte string matches the given regex or
// raw string provided to the matcher.
func (olm *OutputLineMatcher) Matches(s []byte) bool {
	if olm.reg == nil {
		return bytes.Contains(s, olm.raw)
	}
	return olm.reg.Match(s)
}

// String returns the matcher's raw comparison string.
func (olm *OutputLineMatcher) String() string {
	return string(olm.raw)
}

// UnmarshalJSON unmarshals the startup lines into individual structs for easier
// matching abilities.
func (olm *OutputLineMatcher) UnmarshalJSON(data []byte) error {
	var r string
	if err := json.Unmarshal(data, &r); err != nil {
		return err
	}

	olm.raw = []byte(r)
	if bytes.HasPrefix(olm.raw, []byte("regex:")) && len(olm.raw) > 6 {
		r, err := regexp.Compile(strings.TrimPrefix(string(olm.raw), "regex:"))
		if err != nil {
			log.WithField("error", err).WithField("raw", string(olm.raw)).Warn("failed to compile output line marked as being regex")
		}
		olm.reg = r
	}

	return nil
}

// ProcessStopConfiguration defines what is used when stopping an instance.
type ProcessStopConfiguration struct {
	Type  string `json:"type"`
	Value string `json:"value"`
}

// ProcessConfiguration defines the process configuration for a given server
// instance. This sets what Wings is looking for to mark a server as done
// starting what to do when stopping, and what changes to make to the
// configuration file for a server.
type ProcessConfiguration struct {
	Startup struct {
		Done            []*OutputLineMatcher `json:"done"`
		UserInteraction []string             `json:"user_interaction"`
		StripAnsi       bool                 `json:"strip_ansi"`
	} `json:"startup"`
	Stop               ProcessStopConfiguration   `json:"stop"`
	ConfigurationFiles []parser.ConfigurationFile `json:"configs"`
}

type BackupRemoteUploadResponse struct {
	Parts    []string `json:"parts"`
	PartSize int64    `json:"part_size"`
}

type BackupPart struct {
	ETag       string `json:"etag"`
	PartNumber int    `json:"part_number"`
}

type BackupRequest struct {
	Checksum     string       `json:"checksum"`
	ChecksumType string       `json:"checksum_type"`
	Size         int64        `json:"size"`
	Successful   bool         `json:"successful"`
	Parts        []BackupPart `json:"parts"`
}

type InstallStatusRequest struct {
	Successful bool `json:"successful"`
	Reinstall  bool `json:"reinstall"`
}
