package include

import (
	"agent/defines/derrs"
	"bytes"
	"crypto/rsa"
	"encoding/base64"
	"encoding/json"

	"github.com/google/uuid"
)

type ServerCfg struct {
	// Схема с которой может работать центр. Доступно http/https/tcp.
	Schema string
	// IP-адрес / доменнное имя сервера или тунеля.
	Host string
	// Порт на котором слушает центр или тунель. У http/https и tcp разные порты.
	Port      int
	Handshake ServerCfgHandshake
}

func (s ServerCfg) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"schema":    s.Schema,
		"host":      s.Host,
		"port":      s.Port,
		"handshake": s.Handshake,
	}
	return json.Marshal(jsonData)
}

func (s *ServerCfg) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["schema"].(string); ok {
		s.Schema = val
	}
	if val, ok := jsonData["host"].(string); ok {
		s.Host = val
	}
	if val, ok := jsonData["port"].(float64); ok {
		s.Port = int(val)
	}
	if handshakeData, ok := jsonData["handshake"].(map[string]any); ok {
		handshakeBytes, err := json.Marshal(handshakeData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(handshakeBytes, &s.Handshake); err != nil {
			return err
		}
	}
	return nil
}

type ServerCfgHandshake struct {
	// Уникальный идентификатор ключа. Используется центром для идентификации ключа.
	Value []byte
	// Файл в котором содержится публичный ключ центра. Указывается вручную в "debug" версии клиента.
	KeyFile string
	// Декодированное "pem.Decode(pubKeyData)" содержимое публичного ключа центра.
	// Используется для шифрования auth_data.
	KeyData   []byte
	KeyObject *rsa.PublicKey
}

func (h ServerCfgHandshake) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"value": h.Value,
		"key":   h.KeyData,
	}
	return json.Marshal(jsonData)
}

func (h *ServerCfgHandshake) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if raw, ok := jsonData["value"].(string); ok {
		value, err := base64.StdEncoding.DecodeString(raw)
		if err != nil {
			return err
		}
		h.Value = value
	}
	if raw, ok := jsonData["key"].(string); ok {
		keyData, err := base64.StdEncoding.DecodeString(raw)
		if err != nil {
			return err
		}
		h.KeyData = keyData
	}
	return nil
}

type ClientCfg struct {
	// Имя агента (опционально). Используется для задании имени агента при его регистрации, в дальнейшем
	// игнорируется.
	Name string
	// Клиентский ключ
	Key string
	// Версия клиента (должна существовать на стороне центра, иначе агент не залогинится). Проверку версии можно
	// отключить через ClientKey ("client.check_version").
	Version string
	// Тип клиента (внутреннее поле, не сериализуется в JSON)
	Type string
	// Схема клиента (должна существовать на стороне центра, иначе агент не залогинится)
	Schema string
	// Алгоритм шифрования, который будет применяться для шифрования трафика. Доступные алгоритмы:
	// - NONE: без шифрования, ключ не указывается.
	// - AES_CTR: ключ 16/24/32 байта;
	// - RC4: ключ от 1 до 256 байт;
	// - XOR: ключ 1 байт;
	// - XOR_2: ключ 2 байта;
	// - XOR_4: ключ 4 байта;
	// - XOR_8: ключ 8 байт;
	// - XOR_16: ключ 16 байт;
	// - XOR_32: ключ 32 байта;
	// Примечание:
	//   Название алгоритма зависит от используемой схемы.
	Cipher ClientCipher

	CommandRound    int
	CommandPerRound int

	Interval ClientItenterval
}

func (c ClientCfg) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"name":              c.Name,
		"key":               c.Key,
		"version":           c.Version,
		"schema":            c.Schema,
		"cipher":            c.Cipher,
		"command_round":     c.CommandRound,
		"command_per_round": c.CommandPerRound,
		"interval":          c.Interval,
	}
	return json.Marshal(jsonData)
}

func (c *ClientCfg) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["name"].(string); ok {
		c.Name = val
	}
	if val, ok := jsonData["key"].(string); ok {
		c.Key = val
	}
	if val, ok := jsonData["version"].(string); ok {
		c.Version = val
	}
	if val, ok := jsonData["schema"].(string); ok {
		c.Schema = val
	}
	if cipherData, ok := jsonData["cipher"].(map[string]any); ok {
		cipherBytes, err := json.Marshal(cipherData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(cipherBytes, &c.Cipher); err != nil {
			return err
		}
	}
	if val, ok := jsonData["command_round"].(float64); ok {
		c.CommandRound = int(val)
	}
	if val, ok := jsonData["command_per_round"].(float64); ok {
		c.CommandPerRound = int(val)
	}

	if intervalData, ok := jsonData["interval"].(map[string]any); ok {
		intervalBytes, err := json.Marshal(intervalData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(intervalBytes, &c.Interval); err != nil {
			return err
		}
	}
	return nil
}

type ClientCipher struct {
	// Название алгоритма шифрования
	Name string
	// Ключа шифрования. Генерируется при старте (или используется из конфига).
	// Допустимые размеры ключа для AES_CTR 16/24/32 байта.
	Key []byte
}

func (c ClientCipher) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"name": c.Name,
		"key":  c.Key,
	}
	return json.Marshal(jsonData)
}

func (c *ClientCipher) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["name"].(string); ok {
		c.Name = val
	}
	if val, ok := jsonData["key"].(string); ok {
		key, err := base64.StdEncoding.DecodeString(val)
		if err != nil {
			return err
		}
		c.Key = key
	}
	return nil
}

type DebugCfg struct {
	// Включить отладку
	Active bool
	// UUID агента, с которым залогинится агент. Клиентский ключ должен иметь разрешения CanLoginByAgentUuuid.
	AgentUuid uuid.UUID
	// FIX_ME: Надо придумать, какое униальное значение брать для генерации HWID/DeviceUuid.
	// 1. Можно создать рандомный файл, брать от него хэш.
	// 2. Можно брать лицензионный идентификатор, какого-либо установленного софта.
	//   - Зависит от OS (win/linux/macos).
	//
	// 2. Можно создать ключ в реестре с рандомным значением.
	//   - Для linux/macos не подходит.
	//
	// 3. Можно порыться в реестре и найти уникальные данные.
	//   - Для linux/macos не подходит.
	//
	// 5. Можно брать значения CPU/MotherBoard/HardwareDisk/etc - надо согласовать способ получения этих данных.
	//   - Можно использовать какие-либо библиотеки для этого (определиться с лицензиями, какие можно?)
	//   - Можно использовать winapi (тогда надо писать свои версии для linux/macos)
	//
	// UUID устройства, с которым залогинится агент. У клиентского ключа должен быть активирован режим отладки.
	DeviceUuid uuid.UUID
}

func (c DebugCfg) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"active":      c.Active,
		"agent_uuid":  c.AgentUuid,
		"device_uuid": c.DeviceUuid,
	}
	return json.Marshal(jsonData)
}

func (c *DebugCfg) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["active"].(bool); ok {
		c.Active = val
	}
	if val, ok := jsonData["agent_uuid"].(string); ok {
		uuidVal, err := uuid.Parse(val)
		if err != nil {
			return err
		}
		c.AgentUuid = uuidVal
	}
	if val, ok := jsonData["device_uuid"].(string); ok {
		uuidVal, err := uuid.Parse(val)
		if err != nil {
			return err
		}
		c.DeviceUuid = uuidVal
	}
	return nil
}

type ScheduleCfg struct {
	// Версия графика. Если она меньше чем на сервере, то будет использоваться график с сервера.
	Version int64

	// Рабочие дни и время работы агента. Если не указано, то агент работает круглосуточно.
	Work ScheduleCfgWork

	// Дни, когда агент должен работать вне графика. Если не указано, то агент работает по графику.
	MakeupDays map[string][]int

	// Праздничные дни, когда агент не работает. Если не указано, то агент работает по графику.
	Hollidays map[string][]int
}

func (c ScheduleCfg) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"version":     c.Version,
		"work":        c.Work,
		"makeup_days": c.MakeupDays,
		"holidays":    c.Hollidays,
	}
	return json.Marshal(jsonData)
}

func (c *ScheduleCfg) UnmarshalJSON(data []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()

	jsonData := map[string]any{}
	err := decoder.Decode(&jsonData)
	if err != nil {
		return err
	}

	if val, ok := jsonData["version"].(json.Number); ok {
		version, err := val.Int64()
		if err != nil {
			return derrs.Join(err, derrs.NewUnknownError())
		}
		c.Version = version
	}
	if workData, ok := jsonData["work"].(map[string]any); ok {
		workBytes, err := json.Marshal(workData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(workBytes, &c.Work); err != nil {
			return err
		}
	}
	if makeupDaysData, ok := jsonData["makeup_days"].(map[string]any); ok {
		c.MakeupDays = map[string][]int{}
		for k, v := range makeupDaysData {
			if daysSlice, ok := v.([]any); ok {
				for _, dayVal := range daysSlice {
					if num, ok := dayVal.(json.Number); ok {
						dayFloat, _ := num.Int64()
						c.MakeupDays[k] = append(c.MakeupDays[k], int(dayFloat))
					}
				}
			}
		}
	}
	if holidaysData, ok := jsonData["holidays"].(map[string]any); ok {
		c.Hollidays = map[string][]int{}
		for k, v := range holidaysData {
			if daysSlice, ok := v.([]any); ok {
				for _, dayVal := range daysSlice {
					if num, ok := dayVal.(json.Number); ok {
						dayFloat, _ := num.Int64()
						c.Hollidays[k] = append(c.Hollidays[k], int(dayFloat))
					}
				}
			}
		}
	}
	return nil
}

type ScheduleCfgWorkDays struct {
	Monday    bool
	Tuesday   bool
	Wednesday bool
	Thursday  bool
	Friday    bool
	Saturday  bool
	Sunday    bool
}

func (d ScheduleCfgWorkDays) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"monday":    d.Monday,
		"tuesday":   d.Tuesday,
		"wednesday": d.Wednesday,
		"thursday":  d.Thursday,
		"friday":    d.Friday,
		"saturday":  d.Saturday,
		"sunday":    d.Sunday,
	}
	return json.Marshal(jsonData)
}

func (d *ScheduleCfgWorkDays) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["monday"].(bool); ok {
		d.Monday = val
	}
	if val, ok := jsonData["tuesday"].(bool); ok {
		d.Tuesday = val
	}
	if val, ok := jsonData["wednesday"].(bool); ok {
		d.Wednesday = val
	}
	if val, ok := jsonData["thursday"].(bool); ok {
		d.Thursday = val
	}
	if val, ok := jsonData["friday"].(bool); ok {
		d.Friday = val
	}
	if val, ok := jsonData["saturday"].(bool); ok {
		d.Saturday = val
	}
	if val, ok := jsonData["sunday"].(bool); ok {
		d.Sunday = val
	}
	return nil
}

type ScheduleCfgWork struct {
	// Дни недели, в которые работает агент. Если все значения равны false, то агент перестает работать. Но
	// независимо от настроект этого раздела, агент будет работать в дни, которые указанные в "makeup_days".
	Days ScheduleCfgWorkDays
	// Время работы агента в рабочие дни. Если все значения равны 0, то агент работает круглосуточно.
	Time ScheduleCfgWorkTime
}

func (t ScheduleCfgWork) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"days": t.Days,
		"time": t.Time,
	}
	return json.Marshal(jsonData)
}

func (t *ScheduleCfgWork) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if daysData, ok := jsonData["days"].(map[string]any); ok {
		daysBytes, err := json.Marshal(daysData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(daysBytes, &t.Days); err != nil {
			return err
		}
	}
	if timeData, ok := jsonData["time"].(map[string]any); ok {
		timeBytes, err := json.Marshal(timeData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(timeBytes, &t.Time); err != nil {
			return err
		}
	}
	return nil
}

type ScheduleCfgWorkTime struct {
	// Начало рабочего дня. Формат: "HH:MM".
	Start ScheduleCfgWorkTimeItem
	// Конец рабочего дня. Формат: "HH:MM".
	End ScheduleCfgWorkTimeItem
}

func (t ScheduleCfgWorkTime) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"start": t.Start,
		"end":   t.End,
	}
	return json.Marshal(jsonData)
}

func (t *ScheduleCfgWorkTime) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if startData, ok := jsonData["start"].(map[string]any); ok {
		startBytes, err := json.Marshal(startData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(startBytes, &t.Start); err != nil {
			return err
		}
	}
	if endData, ok := jsonData["end"].(map[string]any); ok {
		endBytes, err := json.Marshal(endData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(endBytes, &t.End); err != nil {
			return err
		}
	}
	return nil
}

type ScheduleCfgWorkTimeItem struct {
	Hour   int
	Minute int
}

func (i ScheduleCfgWorkTimeItem) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"hour":   i.Hour,
		"minute": i.Minute,
	}
	return json.Marshal(jsonData)
}

func (i *ScheduleCfgWorkTimeItem) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["hour"].(float64); ok {
		i.Hour = int(val)
	}
	if val, ok := jsonData["minute"].(float64); ok {
		i.Minute = int(val)
	}
	return nil
}

type AppConfig struct {
	Version  int64
	Server   ServerCfg
	Client   ClientCfg
	Debug    DebugCfg
	Schedule ScheduleCfg
}

func (c AppConfig) MarshalJSON() ([]byte, error) {
	cfg := map[string]any{
		"version":  c.Version,
		"server":   c.Server,
		"client":   c.Client,
		"debug":    c.Debug,
		"schedule": c.Schedule,
	}
	return json.Marshal(cfg)
}

func (c *AppConfig) UnmarshalJSON(data []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()

	jsonData := map[string]any{}
	err := decoder.Decode(&jsonData)
	if err != nil {
		return err
	}

	if val, ok := jsonData["version"].(json.Number); ok {
		version, err := val.Int64()
		if err != nil {
			return derrs.Join(err, derrs.NewUnknownError())
		}
		c.Version = version
	}
	if serverData, ok := jsonData["server"].(map[string]any); ok {
		serverBytes, err := json.Marshal(serverData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(serverBytes, &c.Server); err != nil {
			return err
		}
	}
	if clientData, ok := jsonData["client"].(map[string]any); ok {
		clientBytes, err := json.Marshal(clientData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(clientBytes, &c.Client); err != nil {
			return err
		}
	}
	if debugData, ok := jsonData["debug"].(map[string]any); ok {
		debugBytes, err := json.Marshal(debugData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(debugBytes, &c.Debug); err != nil {
			return err
		}
	}
	if scheduleData, ok := jsonData["schedule"].(map[string]any); ok {
		scheduleBytes, err := json.Marshal(scheduleData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(scheduleBytes, &c.Schedule); err != nil {
			return err
		}
	}
	return nil
}

type AgentRemoteConfig struct {
	Version int64
	Client  AgentRemoteConfigClient
	Server  AgentRemoteConfigServer
	Plugins map[string]map[string]any
}

func (c AgentRemoteConfig) MarshalJSON() ([]byte, error) {
	cfg := map[string]any{
		"version": c.Version,
		"client":  c.Client,
		"server":  c.Server,
		"plugins": c.Plugins,
	}
	return json.Marshal(cfg)
}

func (c *AgentRemoteConfig) UnmarshalJSON(data []byte) error {
	decoder := json.NewDecoder(bytes.NewReader(data))
	decoder.UseNumber()
	jsonData := map[string]any{}
	err := decoder.Decode(&jsonData)
	if err != nil {
		return err
	}
	if val, ok := jsonData["version"].(json.Number); ok {
		version, err := val.Int64()
		if err != nil {
			return derrs.Join(err, derrs.NewUnknownError())
		}
		c.Version = version
	}
	if clientData, ok := jsonData["client"].(map[string]any); ok {
		clientBytes, err := json.Marshal(clientData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(clientBytes, &c.Client); err != nil {
			return err
		}
	}
	if serverData, ok := jsonData["server"].(map[string]any); ok {
		serverBytes, err := json.Marshal(serverData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(serverBytes, &c.Server); err != nil {
			return err
		}
	}
	if pluginsData, ok := jsonData["plugins"].(map[string]any); ok {
		c.Plugins = map[string]map[string]any{}
		for pluginName, pluginConfig := range pluginsData {
			if configMap, ok := pluginConfig.(map[string]any); ok {
				c.Plugins[pluginName] = configMap
			}
		}
	}
	return nil
}

type AgentRemoteConfigClient struct {
	Key             string
	CommandRound    int
	CommandPerRound int

	Interval ClientItenterval
}

func (c AgentRemoteConfigClient) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"key":               c.Key,
		"command_round":     c.CommandRound,
		"command_per_round": c.CommandPerRound,
		"interval":          c.Interval,
	}
	return json.Marshal(jsonData)
}

func (c *AgentRemoteConfigClient) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["key"].(string); ok {
		c.Key = val
	}
	if val, ok := jsonData["command_round"].(float64); ok {
		c.CommandRound = int(val)
	}
	if val, ok := jsonData["command_per_round"].(float64); ok {
		c.CommandPerRound = int(val)
	}

	if intervalData, ok := jsonData["interval"].(map[string]any); ok {
		intervalBytes, err := json.Marshal(intervalData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(intervalBytes, &c.Interval); err != nil {
			return err
		}
	}
	return nil
}

type AgentRemoteConfigServer struct {
	Schema    string
	Host      string
	Port      int
	Handshake AgentRemoteConfigServerHandshake
}

func (s AgentRemoteConfigServer) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"schema":    s.Schema,
		"host":      s.Host,
		"port":      s.Port,
		"handshake": s.Handshake,
	}
	return json.Marshal(jsonData)
}

func (s *AgentRemoteConfigServer) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["schema"].(string); ok {
		s.Schema = val
	}
	if val, ok := jsonData["host"].(string); ok {
		s.Host = val
	}
	if val, ok := jsonData["port"].(float64); ok {
		s.Port = int(val)
	}
	if handshakeData, ok := jsonData["handshake"].(map[string]any); ok {
		handshakeBytes, err := json.Marshal(handshakeData)
		if err != nil {
			return err
		}
		if err = json.Unmarshal(handshakeBytes, &s.Handshake); err != nil {
			return err
		}
	}
	return nil
}

type AgentRemoteConfigServerHandshake struct {
	Value   []byte
	KeyData []byte
}

func (h AgentRemoteConfigServerHandshake) MarshalJSON() ([]byte, error) {
	jsonData := map[string]any{
		"value": h.Value,
		"key":   h.KeyData,
	}
	return json.Marshal(jsonData)
}

func (h *AgentRemoteConfigServerHandshake) UnmarshalJSON(data []byte) error {
	jsonData := map[string]any{}
	if err := json.Unmarshal(data, &jsonData); err != nil {
		return err
	}
	if val, ok := jsonData["value"].(string); ok {
		value, err := base64.StdEncoding.DecodeString(val)
		if err != nil {
			return err
		}
		h.Value = value
	}
	if val, ok := jsonData["key"].(string); ok {
		keyData, err := base64.StdEncoding.DecodeString(val)
		if err != nil {
			return err
		}
		h.KeyData = keyData
	}
	return nil
}
