package main

import (
	"flag"
	"fmt"
	"os"
	"strings"

	config "astrology-api/configs"
	"astrology-api/services"
)

// A standalone check that the AccessToken2 implementation produces the bytes
// Agora expects, without having to drive a live consultation to find out.
//
// This exists because the failure mode of a hand-written token is silent: a
// wrong byte produces a structurally valid token that the Agora edge rejects
// with no diagnostic, days later, in an app build. Run this first.
//
//	go run ./cmd/agoratoken -medium CHAT  -channel CHT123 -uid 1042
//	go run ./cmd/agoratoken -medium VIDEO -channel CHT123 -uid 1042 -peer 37
//
// To diff byte-for-byte against Agora's own generator, pin the two values
// that are otherwise random or clock-derived, and give the same numbers to
// the reference implementation:
//
//	go run ./cmd/agoratoken -medium CHAT -channel test -uid 1 -ts 1700000000 -salt 1
//
// Credentials come from .env (AGORA_APP_ID, AGORA_APP_CERTIFICATE) or from
// the flags, so this works before a database exists.
func main() {

	medium := flag.String("medium", "CHAT", "CHAT | AUDIO | VIDEO")
	channel := flag.String("channel", "", "channel name (defaults to a test value)")
	uid := flag.Uint("uid", 0, "this party's users.id")
	peer := flag.Uint("peer", 0, "the other party's users.id")
	ttl := flag.Int("ttl", 3600, "token lifetime in seconds")

	appID := flag.String("app-id", "", "override AGORA_APP_ID")
	appCert := flag.String("app-certificate", "", "override AGORA_APP_CERTIFICATE")

	issueTs := flag.Uint("ts", 0, "pin the issue timestamp, for diffing against Agora's generator")
	salt := flag.Uint("salt", 0, "pin the salt, for diffing against Agora's generator")

	flag.Parse()

	//------------------------------------------------
	// Configuration
	//------------------------------------------------
	//
	// A missing .env is not fatal here: the flags are enough.

	loadEnvQuietly()

	config.LoadAgoraConfig()

	if *appID != "" {
		config.Agora.AppID = strings.TrimSpace(*appID)
	}

	if *appCert != "" {
		config.Agora.AppCertificate = strings.TrimSpace(*appCert)
	}

	if !config.Agora.Enabled() {
		fmt.Println("AGORA_APP_ID and AGORA_APP_CERTIFICATE are not set.")
		fmt.Println("Pass -app-id and -app-certificate, or add them to .env.")
		os.Exit(1)
	}

	//------------------------------------------------
	// Inputs
	//------------------------------------------------

	selected := strings.ToUpper(strings.TrimSpace(*medium))

	if selected != "CHAT" && selected != "AUDIO" && selected != "VIDEO" {
		fmt.Println("-medium must be CHAT, AUDIO or VIDEO")
		os.Exit(1)
	}

	name := *channel

	if name == "" {
		name = "agoratoken-check"
	}

	party := uint32(*uid)

	if party == 0 {
		party = 1
	}

	other := uint32(*peer)

	if other == 0 {
		other = party + 1
	}

	//------------------------------------------------
	// Mint
	//------------------------------------------------

	result, err := services.MintForDiagnostics(
		config.Agora,
		selected,
		name,
		party,
		other,
		*ttl,
		uint32(*issueTs),
		uint32(*salt),
	)

	if err != nil {
		fmt.Printf("failed to mint: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("app id        %s\n", config.Agora.AppID)
	fmt.Printf("medium        %s\n", selected)
	fmt.Printf("channel       %s\n", result.ChannelName)
	fmt.Printf("account       %s  (peer %s)\n", result.RTMAccount, result.PeerRTMAccount)
	fmt.Printf("expires       %s  (%d seconds)\n", result.ExpiresAt, result.ExpiresInSeconds)
	fmt.Println()
	fmt.Printf("RTM token\n%s\n", result.RTMToken)

	if result.RTCToken != "" {
		fmt.Println()
		fmt.Printf("RTC token (uid %d, role %s)\n%s\n", result.RTCUID, result.RTCRole, result.RTCToken)
	} else {
		fmt.Println()
		fmt.Println("RTC token     none - CHAT carries no media")
	}

	if *issueTs == 0 || *salt == 0 {
		fmt.Println()
		fmt.Println("Note: the issue timestamp and salt were generated, so this token differs")
		fmt.Println("on every run. Pass -ts and -salt to pin them for a byte-for-byte diff")
		fmt.Println("against Agora's own generator.")
	}
}

// loadEnvQuietly reads .env if there is one. config.LoadEnv() calls log.Fatal
// when it finds nothing and the process environment is also empty, which is a
// perfectly normal way to run this tool.
func loadEnvQuietly() {

	defer func() {
		_ = recover()
	}()

	if _, err := os.Stat(".env"); err != nil {
		return
	}

	config.LoadEnv()
}
