package config

import (
	"fmt"
	"net"
	"strings"

	"github.com/spf13/viper"
)

type Mode int

const (
	S1 Mode = iota + 1
	S15
	S2
	S25
	S3
	S35
	S4
	S5
	S55
	S6
	S7
	S8
	S85
	S9
	S10
	F1
	F2
	F3
	F4
	F5
	G1
	G2
	G3
	G31
	G32
	GG
	G5
	G51
	G52
	G6
	G61
	G7
	G8
	G81
	G9
	G91
	G10
	G101
	Z1
	Z2
	ZZ
)

var versionStrings = []string{"S1.0", "S1.5", "S2.0", "S2.5", "S3.0", "S3.5", "S4.0", "S5.0", "S5.5", "S6.0", "S7.0",
	"S8.0", "S8.5", "S9.0", "S10", "FW.1", "FW.2", "FW.3", "FW.4", "FW.5", "G1", "G2", "G3", "G3.1", "G3.2", "GG", "G5",
	"G5.1", "G5.2", "G6", "G6.1", "G7", "G8", "G8.1", "G9", "G9.1", "G10", "G10.1", "Z1", "Z2", "ZZ"}

func (m Mode) String() string {
	return versionStrings[m]
}

// Config holds the global server-wide config.
type Config struct {
	Host                      string `mapstructure:"Host"`
	BinPath                   string `mapstructure:"BinPath"`
	Language                  string
	DisableShutdownCountdown  bool     `mapstructure:"DisableShutdownCountdown"` // Skip the in-game shutdown countdown (lets scripts restart the server unattended). Previously named DisableSoftCrash — legacy key still accepted.
	ShutdownCountdownSeconds  int      // Seconds to count down before shutting down (default 10; ignored when DisableShutdownCountdown is true)
	ShutdownDrainSeconds      int      // Additional seconds to wait for sessions to disconnect naturally before force-closing (default 30)
	HideLoginNotice           bool     // Hide the Erupe notice on login
	LoginNotices              []string // MHFML string of the login notices displayed
	PatchServerManifest       string   // Manifest patch server override
	PatchServerFile           string   // File patch server override
	DeleteOnSaveCorruption    bool     // Attempts to save corrupted data will flag the save for deletion
	DisableSaveIntegrityCheck bool     // Skip SHA-256 hash verification on load (needed for cross-server save transfers)
	ClientMode                string
	RealClientMode            Mode
	QuestCacheExpiry          int    // Number of seconds to keep quest data cached
	CommandPrefix             string // The prefix for commands
	AutoCreateAccount         bool   // Automatically create accounts if they don't exist
	LoopDelay                 int    // Delay in milliseconds between each loop iteration
	DefaultCourses            []uint16
	EarthStatus               int32
	EarthID                   int32
	EarthMonsters             []int32
	SaveDumps                 SaveDumpOptions
	Screenshots               ScreenshotsOptions
	Capture                   CaptureOptions

	DebugOptions    DebugOptions
	GameplayOptions GameplayOptions
	Discord         Discord
	Commands        []Command
	Courses         []Course
	Database        Database
	Sign            Sign
	API             API
	Channel         Channel
	Entrance        Entrance
}

type SaveDumpOptions struct {
	Enabled    bool
	RawEnabled bool
	OutputDir  string
}

type ScreenshotsOptions struct {
	Enabled       bool
	Host          string // Destination for screenshots uploaded to BBS
	Port          uint32 // Port for screenshots API
	OutputDir     string
	UploadQuality int //Determines the upload quality to the server
}

// CaptureOptions controls protocol packet capture recording.
type CaptureOptions struct {
	Enabled         bool     // Enable packet capture
	OutputDir       string   // Directory for .mhfr capture files
	ExcludeOpcodes  []uint16 // Opcodes to exclude from capture (e.g., ping, nop, position)
	CaptureSign     bool     // Capture sign server sessions
	CaptureEntrance bool     // Capture entrance server sessions
	CaptureChannel  bool     // Capture channel server sessions
}

// DebugOptions holds various debug/temporary options for use while developing Erupe.
type DebugOptions struct {
	CleanDB             bool   // Automatically wipes the DB on server reset.
	MaxLauncherHR       bool   // Sets the HR returned in the launcher to HR7 so that you can join non-beginner worlds.
	LogInboundMessages  bool   // Log all messages sent to the server
	LogOutboundMessages bool   // Log all messages sent to the clients
	LogMessageData      bool   // Log all bytes transferred as a hexdump
	MaxHexdumpLength    int    // Maximum number of bytes printed when logs are enabled
	DivaOverride        int    // Diva Defense event status
	FestaOverride       int    // Hunter's Festa event status
	TournamentOverride  int    // VS Tournament event status
	DisableTokenCheck   bool   // Disables checking login token exists in the DB (security risk!)
	QuestTools          bool   // Enable various quest debug logs
	AutoQuestBackport   bool   // Automatically backport quest files
	ProxyPort           uint16 // Forces the game to connect to a channel server proxy
	CapLink             CapLinkOptions
}

type CapLinkOptions struct {
	Values []uint16
	Key    string
	Host   string
	Port   int
}

// GameplayOptions has various gameplay modifiers
type GameplayOptions struct {
	MinFeatureWeapons              int       // Minimum number of Active Feature weapons to generate daily
	MaxFeatureWeapons              int       // Maximum number of Active Feature weapons to generate daily
	MaximumNP                      int       // Maximum number of NP held by a player
	MaximumRP                      uint16    // Maximum number of RP held by a player
	MaximumFP                      uint32    // Maximum number of FP held by a player
	TreasureHuntExpiry             uint32    // Seconds until a Clan Treasure Hunt will expire
	TreasureHuntPartnyaCooldown    uint32    // Seconds until a Partnya can be assigned to another Clan Treasure Hunt
	DisableLoginBoost              bool      // Disables the Login Boost system
	DisableBoostTime               bool      // Disables the daily NetCafe Boost Time
	BoostTimeDuration              int       // Second that the NetCafe Boost Time lasts
	ClanMealDuration               int       // Second that a Clan Meal can be activated for after cooking
	ClanMemberLimits               [][]uint8 // Array of maximum Clan Members -> [Rank, Members]
	BonusQuestAllowance            uint32    // Number of Bonus Point Quests to allow daily
	DailyQuestAllowance            uint32    // Number of Daily Quests to allow daily
	LowLatencyRaviente             bool      // Toggles low latency mode for Raviente, can be network intensive
	RegularRavienteMaxPlayers      uint8
	ViolentRavienteMaxPlayers      uint8
	BerserkRavienteMaxPlayers      uint8
	ExtremeRavienteMaxPlayers      uint8
	SmallBerserkRavienteMaxPlayers uint8
	GUrgentRate                    float32 // Adjusts the rate of G Urgent quests spawning
	GCPMultiplier                  float32 // Adjusts the multiplier of GCP rewarded for quest completion
	HRPMultiplier                  float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion
	HRPMultiplierNC                float32 // Adjusts the multiplier of Hunter Rank Points rewarded for quest completion in a NetCafe
	SRPMultiplier                  float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion
	SRPMultiplierNC                float32 // Adjusts the multiplier of Skill Rank Points rewarded for quest completion in a NetCafe
	GRPMultiplier                  float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion
	GRPMultiplierNC                float32 // Adjusts the multiplier of G Rank Points rewarded for quest completion in a NetCafe
	GSRPMultiplier                 float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion
	GSRPMultiplierNC               float32 // Adjusts the multiplier of G Skill Rank Points rewarded for quest completion in a NetCafe
	ZennyMultiplier                float32 // Adjusts the multiplier of Zenny rewarded for quest completion
	ZennyMultiplierNC              float32 // Adjusts the multiplier of Zenny rewarded for quest completion in a NetCafe
	GZennyMultiplier               float32 // Adjusts the multiplier of G Zenny rewarded for quest completion
	GZennyMultiplierNC             float32 // Adjusts the multiplier of G Zenny rewarded for quest completion in a NetCafe
	MaterialMultiplier             float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion
	MaterialMultiplierNC           float32 // Adjusts the multiplier of Monster Materials rewarded for quest completion in a NetCafe
	GMaterialMultiplier            float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion
	GMaterialMultiplierNC          float32 // Adjusts the multiplier of G Rank Monster Materials rewarded for quest completion in a NetCafe
	ExtraCarves                    uint16  // Grant n extra chances to carve ALL carcasses
	ExtraCarvesNC                  uint16  // Grant n extra chances to carve ALL carcasses in a NetCafe
	GExtraCarves                   uint16  // Grant n extra chances to carve ALL G Rank carcasses
	GExtraCarvesNC                 uint16  // Grant n extra chances to carve ALL G Rank carcasses in a NetCafe
	DisableHunterNavi              bool    // Disables the Hunter Navi
	MezFesSoloTickets              uint32  // Number of solo tickets given weekly
	MezFesGroupTickets             uint32  // Number of group tickets given weekly
	MezFesDuration                 int     // Seconds that MezFes will last for weekly (from 12AM Mon backwards)
	MezFesSwitchMinigame           bool    // Swaps out Volpakkun Together for Tokotoko Partnya
	EnableKaijiEvent               bool    // Enables the Kaiji event in the Rasta Bar
	EnableHiganjimaEvent           bool    // Enables the Higanjima event in the Rasta Bar
	EnableNierEvent                bool    // Enables the Nier event in the Rasta Bar
	DisableRoad                    bool    // Disables the Hunting Road
	SeasonOverride                 bool    // Overrides the Quest Season with the current Mezeporta Season
}

// Discord holds the discord integration config.
type Discord struct {
	Enabled      bool
	BotToken     string
	RelayChannel DiscordRelay
}

type DiscordRelay struct {
	Enabled          bool
	MaxMessageLength int
	RelayChannelID   string
}

// Command is a channelserver chat command
type Command struct {
	Name        string
	Enabled     bool
	Description string
	Prefix      string
}

// Course represents a course within MHF
type Course struct {
	Name    string
	Enabled bool
}

// Database holds the postgres database config.
type Database struct {
	Host     string
	Port     int
	User     string
	Password string
	Database string
}

// Sign holds the sign server config.
type Sign struct {
	Enabled bool
	Port    int
}

// API holds server config
type API struct {
	Enabled     bool
	Port        int
	PatchServer string
	Banners     []APISignBanner
	Messages    []APISignMessage
	Links       []APISignLink
	LandingPage LandingPage
}

// LandingPage holds config for the browser-facing landing page at /.
type LandingPage struct {
	Enabled bool   // Toggle the landing page on/off
	Title   string // Page title (e.g. "My Frontier Server")
	Content string // Body content — supports raw HTML
}

type APISignBanner struct {
	Src  string `json:"src"`  // Displayed image URL
	Link string `json:"link"` // Link accessed on click
}

type APISignMessage struct {
	Message string `json:"message"` // Displayed message
	Date    int64  `json:"date"`    // Displayed date
	Kind    int    `json:"kind"`    // 0 for 'Default', 1 for 'New'
	Link    string `json:"link"`    // Link accessed on click
}

type APISignLink struct {
	Name string `json:"name"` // Displayed name
	Icon string `json:"icon"` // Displayed icon. It will be cast as a monochrome color as long as it is transparent.
	Link string `json:"link"` // Link accessed on click
}

type Channel struct {
	Enabled bool
}

// Entrance holds the entrance server config.
type Entrance struct {
	Enabled bool
	Port    uint16
	Entries []EntranceServerInfo
}

// EntranceServerInfo represents an entry in the serverlist.
type EntranceServerInfo struct {
	IP          string
	Type        uint8  // Server type. 0=?, 1=open, 2=cities, 3=newbie, 4=bar
	Season      uint8  // Server activity. 0 = green, 1 = orange, 2 = blue
	Recommended uint8  // Something to do with server recommendation on 0, 3, and 5.
	Name        string // Server name, 66 byte null terminated Shift-JIS(JP) or Big5(TW).
	Description string // Server description
	// 4096(PC, PS3/PS4)?, 8258(PC, PS3/PS4)?, 8192 == nothing?
	// THIS ONLY EXISTS IF Binary8Header.type == "SV2", NOT "SVR"!
	AllowedClientFlags uint32

	Channels []EntranceChannelInfo
}

// EntranceChannelInfo represents an entry in a server's channel list.
type EntranceChannelInfo struct {
	Port           uint16
	MaxPlayers     uint16
	CurrentPlayers uint16
	Enabled        *bool // nil defaults to true for backward compatibility
}

// IsEnabled returns whether this channel is enabled. Defaults to true if Enabled is nil.
func (c *EntranceChannelInfo) IsEnabled() bool {
	if c.Enabled == nil {
		return true
	}
	return *c.Enabled
}

// getOutboundIP4 gets the preferred outbound ip4 of this machine
// From https://stackoverflow.com/a/37382208
func getOutboundIP4() (net.IP, error) {
	conn, err := net.Dial("udp4", "8.8.8.8:80")
	if err != nil {
		return nil, fmt.Errorf("detecting outbound IP: %w", err)
	}
	defer func() { _ = conn.Close() }()

	localAddr := conn.LocalAddr().(*net.UDPAddr)

	return localAddr.IP.To4(), nil
}

// registerDefaults sets all sane defaults via Viper so that a minimal
// config.json (just database credentials) produces a fully working server.
func registerDefaults() {
	// Top-level settings
	viper.SetDefault("Language", "jp")
	viper.SetDefault("BinPath", "bin")
	viper.SetDefault("HideLoginNotice", true)
	viper.SetDefault("LoginNotices", []string{
		"<BODY><CENTER><SIZE_3><C_4>Welcome to Erupe!",
	})
	viper.SetDefault("ClientMode", "ZZ")
	viper.SetDefault("QuestCacheExpiry", 300)
	viper.SetDefault("CommandPrefix", "!")
	viper.SetDefault("AutoCreateAccount", true)
	viper.SetDefault("LoopDelay", 50)
	viper.SetDefault("ShutdownCountdownSeconds", 10)
	viper.SetDefault("ShutdownDrainSeconds", 30)
	// Back-compat: old configs use DisableSoftCrash. RegisterAlias makes Viper
	// treat reads/writes of the old key as the new key, so existing
	// config.json files keep working without modification.
	viper.RegisterAlias("DisableSoftCrash", "DisableShutdownCountdown")
	viper.SetDefault("DefaultCourses", []uint16{1, 23, 24})
	viper.SetDefault("EarthMonsters", []int32{0, 0, 0, 0})

	// SaveDumps
	viper.SetDefault("SaveDumps", SaveDumpOptions{
		Enabled:   true,
		OutputDir: "save-backups",
	})

	// Screenshots
	viper.SetDefault("Screenshots", ScreenshotsOptions{
		Enabled:       true,
		Host:          "127.0.0.1",
		Port:          8080,
		OutputDir:     "screenshots",
		UploadQuality: 100,
	})

	// Capture
	viper.SetDefault("Capture", CaptureOptions{
		OutputDir:       "captures",
		CaptureSign:     true,
		CaptureEntrance: true,
		CaptureChannel:  true,
	})

	// DebugOptions (dot-notation for per-field merge)
	viper.SetDefault("DebugOptions.MaxHexdumpLength", 256)
	viper.SetDefault("DebugOptions.FestaOverride", -1)
	viper.SetDefault("DebugOptions.AutoQuestBackport", true)
	viper.SetDefault("DebugOptions.CapLink", CapLinkOptions{
		Values: []uint16{51728, 20000, 51729, 1, 20000},
		Port:   80,
	})

	// GameplayOptions (dot-notation — critical to avoid zeroing multipliers)
	viper.SetDefault("GameplayOptions.MaxFeatureWeapons", 1)
	viper.SetDefault("GameplayOptions.MaximumNP", 100000)
	viper.SetDefault("GameplayOptions.MaximumRP", uint16(50000))
	viper.SetDefault("GameplayOptions.MaximumFP", uint32(120000))
	viper.SetDefault("GameplayOptions.TreasureHuntExpiry", uint32(604800))
	viper.SetDefault("GameplayOptions.BoostTimeDuration", 7200)
	viper.SetDefault("GameplayOptions.ClanMealDuration", 3600)
	viper.SetDefault("GameplayOptions.ClanMemberLimits", [][]uint8{{0, 30}, {3, 40}, {7, 50}, {10, 60}})
	viper.SetDefault("GameplayOptions.BonusQuestAllowance", uint32(3))
	viper.SetDefault("GameplayOptions.DailyQuestAllowance", uint32(1))
	viper.SetDefault("GameplayOptions.RegularRavienteMaxPlayers", uint8(8))
	viper.SetDefault("GameplayOptions.ViolentRavienteMaxPlayers", uint8(8))
	viper.SetDefault("GameplayOptions.BerserkRavienteMaxPlayers", uint8(32))
	viper.SetDefault("GameplayOptions.ExtremeRavienteMaxPlayers", uint8(32))
	viper.SetDefault("GameplayOptions.SmallBerserkRavienteMaxPlayers", uint8(8))
	viper.SetDefault("GameplayOptions.GUrgentRate", float64(0.10))
	// All reward multipliers default to 1.0 — without this, Go's zero value
	// (0.0) would zero out all quest rewards for minimal configs.
	for _, key := range []string{
		"GCPMultiplier", "HRPMultiplier", "HRPMultiplierNC",
		"SRPMultiplier", "SRPMultiplierNC", "GRPMultiplier", "GRPMultiplierNC",
		"GSRPMultiplier", "GSRPMultiplierNC", "ZennyMultiplier", "ZennyMultiplierNC",
		"GZennyMultiplier", "GZennyMultiplierNC", "MaterialMultiplier", "MaterialMultiplierNC",
		"GMaterialMultiplier", "GMaterialMultiplierNC",
	} {
		viper.SetDefault("GameplayOptions."+key, float64(1.0))
	}
	viper.SetDefault("GameplayOptions.MezFesSoloTickets", uint32(5))
	viper.SetDefault("GameplayOptions.MezFesGroupTickets", uint32(1))
	viper.SetDefault("GameplayOptions.MezFesDuration", 172800)

	// Discord
	viper.SetDefault("Discord.RelayChannel.MaxMessageLength", 183)

	// Commands (whole-struct default — replaced entirely if user provides any)
	viper.SetDefault("Commands", []Command{
		{Name: "Help", Enabled: true, Description: "Show enabled chat commands", Prefix: "help"},
		{Name: "Rights", Enabled: false, Description: "Overwrite the Rights value on your account", Prefix: "rights"},
		{Name: "Raviente", Enabled: true, Description: "Various Raviente siege commands", Prefix: "ravi"},
		{Name: "Teleport", Enabled: false, Description: "Teleport to specified coordinates", Prefix: "tele"},
		{Name: "Reload", Enabled: true, Description: "Reload all players in your Land", Prefix: "reload"},
		{Name: "KeyQuest", Enabled: false, Description: "Overwrite your HR Key Quest progress", Prefix: "kqf"},
		{Name: "Course", Enabled: true, Description: "Toggle Courses on your account", Prefix: "course"},
		{Name: "PSN", Enabled: true, Description: "Link a PlayStation Network ID to your account", Prefix: "psn"},
		{Name: "Discord", Enabled: true, Description: "Generate a token to link your Discord account", Prefix: "discord"},
		{Name: "Ban", Enabled: false, Description: "Ban/Temp Ban a user", Prefix: "ban"},
		{Name: "Timer", Enabled: true, Description: "Toggle the Quest timer", Prefix: "timer"},
		{Name: "Playtime", Enabled: true, Description: "Show your playtime", Prefix: "playtime"},
		{Name: "Language", Enabled: true, Description: "Show or change your preferred language (en|jp|fr|es)", Prefix: "lang"},
	})

	// Courses
	viper.SetDefault("Courses", []Course{
		{Name: "HunterLife", Enabled: true},
		{Name: "Extra", Enabled: true},
		{Name: "Premium", Enabled: true},
		{Name: "Assist", Enabled: false},
		{Name: "N", Enabled: false},
		{Name: "Hiden", Enabled: false},
		{Name: "HunterSupport", Enabled: false},
		{Name: "NBoost", Enabled: false},
		{Name: "NetCafe", Enabled: true},
		{Name: "HLRenewing", Enabled: true},
		{Name: "EXRenewing", Enabled: true},
	})

	// Database (Password deliberately has no default)
	viper.SetDefault("Database.Host", "localhost")
	viper.SetDefault("Database.Port", 5432)
	viper.SetDefault("Database.User", "postgres")
	viper.SetDefault("Database.Database", "erupe")

	// Sign server
	viper.SetDefault("Sign.Enabled", true)
	viper.SetDefault("Sign.Port", 53312)

	// API server
	viper.SetDefault("API.Enabled", true)
	viper.SetDefault("API.Port", 8080)
	viper.SetDefault("API.LandingPage", LandingPage{
		Enabled: true,
		Title:   "My Frontier Server",
		Content: "<p>Welcome! Server is running.</p>",
	})

	// Channel server
	viper.SetDefault("Channel.Enabled", true)

	// Entrance server
	viper.SetDefault("Entrance.Enabled", true)
	viper.SetDefault("Entrance.Port", uint16(53310))
	boolTrue := true
	viper.SetDefault("Entrance.Entries", []EntranceServerInfo{
		{
			Name: "Newbie", Type: 3, Recommended: 2,
			Channels: []EntranceChannelInfo{
				{Port: 54001, MaxPlayers: 100, Enabled: &boolTrue},
				{Port: 54002, MaxPlayers: 100, Enabled: &boolTrue},
			},
		},
		{
			Name: "Normal", Type: 1,
			Channels: []EntranceChannelInfo{
				{Port: 54003, MaxPlayers: 100, Enabled: &boolTrue},
				{Port: 54004, MaxPlayers: 100, Enabled: &boolTrue},
			},
		},
		{
			Name: "Cities", Type: 2,
			Channels: []EntranceChannelInfo{
				{Port: 54005, MaxPlayers: 100, Enabled: &boolTrue},
			},
		},
		{
			Name: "Tavern", Type: 4,
			Channels: []EntranceChannelInfo{
				{Port: 54006, MaxPlayers: 100, Enabled: &boolTrue},
			},
		},
		{
			Name: "Return", Type: 5,
			Channels: []EntranceChannelInfo{
				{Port: 54007, MaxPlayers: 100, Enabled: &boolTrue},
			},
		},
		{
			Name: "MezFes", Type: 6, Recommended: 6,
			Channels: []EntranceChannelInfo{
				{Port: 54008, MaxPlayers: 100, Enabled: &boolTrue},
			},
		},
	})
}

// LoadConfig loads the given config toml file.
func LoadConfig() (*Config, error) {
	viper.SetConfigName("config")
	viper.AddConfigPath(".")

	registerDefaults()

	err := viper.ReadInConfig()
	if err != nil {
		return nil, err
	}

	c := &Config{}
	err = viper.Unmarshal(c)
	if err != nil {
		return nil, err
	}

	if c.Host == "" {
		ip, err := getOutboundIP4()
		if err != nil {
			return nil, fmt.Errorf("failed to detect host IP: %w", err)
		}
		c.Host = ip.To4().String()
	}

	for i := range versionStrings {
		if strings.ToUpper(c.ClientMode) == versionStrings[i] {
			c.RealClientMode = Mode(i + 1)
			c.ClientMode = strings.ToUpper(c.ClientMode)
			if c.RealClientMode <= G101 {
				c.ClientMode += " (Debug only)"
			}
		}
	}
	if c.RealClientMode == 0 {
		c.ClientMode = versionStrings[len(versionStrings)-1]
		c.RealClientMode = ZZ
	}

	if c.GameplayOptions.MinFeatureWeapons > c.GameplayOptions.MaxFeatureWeapons {
		c.GameplayOptions.MinFeatureWeapons = c.GameplayOptions.MaxFeatureWeapons
	}

	return c, nil
}
