// Copyright (c) 2021 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package types contains various structs and other types used by whatsmeow.
package types

import (
	"database/sql"
	"database/sql/driver"
	"fmt"
	"strconv"
	"strings"

	signalProtocol "go.mau.fi/libsignal/protocol"
)

// Known JID servers on WhatsApp
const (
	DefaultUserServer = "s.whatsapp.net"
	GroupServer       = "g.us"
	LegacyUserServer  = "c.us"
	BroadcastServer   = "broadcast"
)

// Some JIDs that are contacted often.
var (
	EmptyJID            = JID{}
	GroupServerJID      = NewJID("", GroupServer)
	ServerJID           = NewJID("", DefaultUserServer)
	BroadcastServerJID  = NewJID("", BroadcastServer)
	StatusBroadcastJID  = NewJID("status", BroadcastServer)
	PSAJID              = NewJID("0", LegacyUserServer)
	OfficialBusinessJID = NewJID("16505361212", LegacyUserServer)
)

// MessageID is the internal ID of a WhatsApp message.
type MessageID = string

// JID represents a WhatsApp user ID.
//
// There are two types of JIDs: regular JID pairs (user and server) and AD-JIDs (user, agent and device).
// AD JIDs are only used to refer to specific devices of users, so the server is always s.whatsapp.net (DefaultUserServer).
// Regular JIDs can be used for entities on any servers (users, groups, broadcasts).
type JID struct {
	User   string
	Agent  uint8
	Device uint8
	Server string
	AD     bool
}

// UserInt returns the user as an integer. This is only safe to run on normal users, not on groups or broadcast lists.
func (jid JID) UserInt() uint64 {
	number, _ := strconv.ParseUint(jid.User, 10, 64)
	return number
}

// ToNonAD returns a version of the JID struct that doesn't have the agent and device set.
func (jid JID) ToNonAD() JID {
	if jid.AD {
		return JID{
			User:   jid.User,
			Server: DefaultUserServer,
		}
	} else {
		return jid
	}
}

// SignalAddress returns the Signal protocol address for the user.
func (jid JID) SignalAddress() *signalProtocol.SignalAddress {
	user := jid.User
	if jid.Agent != 0 {
		user = fmt.Sprintf("%s_%d", jid.User, jid.Agent)
	}
	return signalProtocol.NewSignalAddress(user, uint32(jid.Device))
}

// IsBroadcastList returns true if the JID is a broadcast list, but not the status broadcast.
func (jid JID) IsBroadcastList() bool {
	return jid.Server == BroadcastServer && jid.User != StatusBroadcastJID.User
}

// NewADJID creates a new AD JID.
func NewADJID(user string, agent, device uint8) JID {
	return JID{
		User:   user,
		Agent:  agent,
		Device: device,
		Server: DefaultUserServer,
		AD:     true,
	}
}

func parseADJID(user string) (JID, error) {
	var fullJID JID
	fullJID.AD = true
	fullJID.Server = DefaultUserServer

	dotIndex := strings.IndexRune(user, '.')
	colonIndex := strings.IndexRune(user, ':')
	if dotIndex < 0 || colonIndex < 0 || colonIndex+1 <= dotIndex {
		return fullJID, fmt.Errorf("failed to parse ADJID: missing separators")
	}

	fullJID.User = user[:dotIndex]
	agent, err := strconv.Atoi(user[dotIndex+1 : colonIndex])
	if err != nil {
		return fullJID, fmt.Errorf("failed to parse agent from JID: %w", err)
	} else if agent < 0 || agent > 255 {
		return fullJID, fmt.Errorf("failed to parse agent from JID: invalid value (%d)", agent)
	}
	device, err := strconv.Atoi(user[colonIndex+1:])
	if err != nil {
		return fullJID, fmt.Errorf("failed to parse device from JID: %w", err)
	} else if device < 0 || device > 255 {
		return fullJID, fmt.Errorf("failed to parse device from JID: invalid value (%d)", device)
	}
	fullJID.Agent = uint8(agent)
	fullJID.Device = uint8(device)
	return fullJID, nil
}

// ParseJID parses a JID out of the given string. It supports both regular and AD JIDs.
func ParseJID(jid string) (JID, error) {
	parts := strings.Split(jid, "@")
	if len(parts) == 1 {
		return NewJID("", parts[0]), nil
	} else if strings.ContainsRune(parts[0], ':') && strings.ContainsRune(parts[0], '.') && parts[1] == DefaultUserServer {
		return parseADJID(parts[0])
	}
	return NewJID(parts[0], parts[1]), nil
}

// NewJID creates a new regular JID.
func NewJID(user, server string) JID {
	return JID{
		User:   user,
		Server: server,
	}
}

// String converts the JID to a string representation.
// The output string can be parsed with ParseJID, except for JIDs with no User part specified.
func (jid JID) String() string {
	if jid.AD {
		return fmt.Sprintf("%s.%d:%d@%s", jid.User, jid.Agent, jid.Device, jid.Server)
	} else if len(jid.User) > 0 {
		return fmt.Sprintf("%s@%s", jid.User, jid.Server)
	} else {
		return jid.Server
	}
}

// MarshalText implements encoding.TextMarshaler for JID
func (jid JID) MarshalText() ([]byte, error) {
	return []byte(jid.String()), nil
}

// UnmarshalText implements encoding.TextUnmarshaler for JID
func (jid *JID) UnmarshalText(val []byte) error {
	out, err := ParseJID(string(val))
	if err != nil {
		return err
	}
	*jid = out
	return nil
}

// IsEmpty returns true if the JID has no server (which is required for all JIDs).
func (jid JID) IsEmpty() bool {
	return len(jid.Server) == 0
}

var _ sql.Scanner = (*JID)(nil)

// Scan scans the given SQL value into this JID.
func (jid *JID) Scan(src interface{}) error {
	if src == nil {
		return nil
	}
	var out JID
	var err error
	switch val := src.(type) {
	case string:
		out, err = ParseJID(val)
	case []byte:
		out, err = ParseJID(string(val))
	default:
		err = fmt.Errorf("unsupported type %T for scanning JID", val)
	}
	if err != nil {
		return err
	}
	*jid = out
	return nil
}

// Value returns the string representation of the JID as a value that the SQL package can use.
func (jid JID) Value() (driver.Value, error) {
	if len(jid.Server) == 0 {
		return nil, nil
	}
	return jid.String(), nil
}
