package signserver

import (
	"database/sql"
	"encoding/hex"
	"erupe-ce/common/stringsupport"
	"net"
	"strings"
	"sync"

	"erupe-ce/common/byteframe"
	"erupe-ce/network"

	"go.uber.org/zap"
)

type client int

const (
	PC100 client = iota
	VITA
	PS3
	PS4
	WIIU
)

// Session holds state for the sign server connection.
type Session struct {
	sync.Mutex
	logger         *zap.Logger
	server         *Server
	rawConn        net.Conn
	cryptConn      network.Conn
	client         client
	psn            string
	captureCleanup func()
}

func (s *Session) work() {
	pkt, err := s.cryptConn.ReadPacket()

	if s.server.erupeConfig.DebugOptions.LogInboundMessages {
		s.logger.Debug("Inbound packet", zap.Int("bytes", len(pkt)), zap.String("data", hex.Dump(pkt)))
	}

	if err != nil {
		return
	}
	err = s.handlePacket(pkt)
	if err != nil {
		return
	}
}

func (s *Session) handlePacket(pkt []byte) error {
	bf := byteframe.NewByteFrameFromBytes(pkt)
	reqType := string(bf.ReadNullTerminatedBytes())
	switch reqType[:len(reqType)-3] {
	case "DLTSKEYSIGN:", "DSGN:", "SIGN:":
		s.handleDSGN(bf)
	case "PS4SGN:":
		s.client = PS4
		s.handlePSSGN(bf)
	case "PS3SGN:":
		s.client = PS3
		s.handlePSSGN(bf)
	case "VITASGN:":
		s.client = VITA
		s.handlePSSGN(bf)
	case "WIIUSGN:":
		s.client = WIIU
		s.handleWIIUSGN(bf)
	case "VITACOGLNK:", "COGLNK:":
		s.handlePSNLink(bf)
	case "DELETE:":
		token := string(bf.ReadNullTerminatedBytes())
		characterID := int(bf.ReadUint32())
		tokenID := bf.ReadUint32()
		err := s.server.deleteCharacter(characterID, token, tokenID)
		if err == nil {
			s.logger.Info("Deleted character", zap.Int("CharacterID", characterID))
			_ = s.cryptConn.SendPacket([]byte{0x01}) // DEL_SUCCESS
		}
	default:
		s.logger.Warn("Unknown request", zap.String("reqType", reqType))
		if s.server.erupeConfig.DebugOptions.LogInboundMessages {
			s.logger.Debug("Unknown inbound packet", zap.Int("bytes", len(pkt)), zap.String("data", hex.Dump(pkt)))
		}
	}
	return nil
}

func (s *Session) authenticate(username string, password string) {
	newCharaReq := false
	if username[len(username)-1] == 43 { // '+'
		username = username[:len(username)-1]
		newCharaReq = true
	}
	bf := byteframe.NewByteFrame()
	uid, resp := s.server.validateLogin(username, password)
	switch resp {
	case SIGN_SUCCESS:
		if newCharaReq {
			_ = s.server.newUserChara(uid)
		}
		bf.WriteBytes(s.makeSignResponse(uid))
	default:
		bf.WriteUint8(uint8(resp))
	}
	if s.server.erupeConfig.DebugOptions.LogOutboundMessages {
		s.logger.Debug("Outbound packet", zap.Int("bytes", len(bf.Data())), zap.String("data", hex.Dump(bf.Data())))
	}
	_ = s.cryptConn.SendPacket(bf.Data())
}

func (s *Session) handleWIIUSGN(bf *byteframe.ByteFrame) {
	_ = bf.ReadBytes(1)
	wiiuKey := string(bf.ReadBytes(64))
	uid, err := s.server.userRepo.GetByWiiUKey(wiiuKey)
	if err != nil {
		if err == sql.ErrNoRows {
			s.logger.Info("Unlinked Wii U attempted to authenticate", zap.String("Key", wiiuKey))
			s.sendCode(SIGN_ECOGLINK)
			return
		}
		s.sendCode(SIGN_EABORT)
		return
	}
	_ = s.cryptConn.SendPacket(s.makeSignResponse(uid))
}

func (s *Session) handlePSSGN(bf *byteframe.ByteFrame) {
	// Prevent reading malformed request
	if s.client != PS4 {
		if len(bf.DataFromCurrent()) < 128 {
			s.sendCode(SIGN_EABORT)
			return
		}

		_ = bf.ReadNullTerminatedBytes() // VITA = 0000000256, PS3 = 0000000255
		_ = bf.ReadBytes(2)              // VITA = 1, PS3 = !
		_ = bf.ReadBytes(82)
	}
	s.psn = string(bf.ReadNullTerminatedBytes())
	uid, err := s.server.userRepo.GetByPSNID(s.psn)
	if err != nil {
		if err == sql.ErrNoRows {
			_ = s.cryptConn.SendPacket(s.makeSignResponse(0))
			return
		}
		s.sendCode(SIGN_EABORT)
		return
	}
	_ = s.cryptConn.SendPacket(s.makeSignResponse(uid))
}

func (s *Session) handlePSNLink(bf *byteframe.ByteFrame) {
	_ = bf.ReadNullTerminatedBytes() // Client ID
	credStr := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
	credentials := strings.Split(credStr, "\n")
	tok := string(bf.ReadNullTerminatedBytes())
	uid, resp := s.server.validateLogin(credentials[0], credentials[1])
	if resp == SIGN_SUCCESS && uid > 0 {
		psn, err := s.server.sessionRepo.GetPSNIDByToken(tok)
		if err != nil {
			s.sendCode(SIGN_ECOGLINK)
			return
		}

		// Since we check for the psn_id, this will never run
		exists, err := s.server.userRepo.CountByPSNID(psn)
		if err != nil {
			s.sendCode(SIGN_ECOGLINK)
			return
		} else if exists > 0 {
			s.sendCode(SIGN_EPSI)
			return
		}

		currentPSN, err := s.server.userRepo.GetPSNIDForUsername(credentials[0])
		if err != nil {
			s.sendCode(SIGN_ECOGLINK)
			return
		} else if currentPSN != "" {
			s.sendCode(SIGN_EMBID)
			return
		}

		err = s.server.userRepo.SetPSNID(credentials[0], psn)
		if err == nil {
			s.sendCode(SIGN_SUCCESS)
			return
		}
	}
	s.sendCode(SIGN_ECOGLINK)
}

func (s *Session) handleDSGN(bf *byteframe.ByteFrame) {
	user := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
	pass := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
	_ = string(bf.ReadNullTerminatedBytes()) // Unk
	s.authenticate(user, pass)
}

func (s *Session) sendCode(id RespID) {
	_ = s.cryptConn.SendPacket([]byte{byte(id)})
}
