package rdp

import (
	"context"
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/praetorian-inc/fingerprintx/pkg/plugins"
	"github.com/praetorian-inc/fingerprintx/pkg/plugins/services/rdp"
	"github.com/projectdiscovery/nuclei/v3/pkg/protocols/common/protocolstate"
)

type (
	// IsRDPResponse is the response from the IsRDP function.
	// this is returned by IsRDP function.
	// @example
	// ```javascript
	// const rdp = require('nuclei/rdp');
	// const isRDP = rdp.IsRDP('acme.com', 3389);
	// log(toJSON(isRDP));
	// ```
	IsRDPResponse struct {
		IsRDP bool
		OS    string
	}
)

// IsRDP checks if the given host and port are running rdp server.
// If connection is successful, it returns true.
// If connection is unsuccessful, it returns false and error.
// The Name of the OS is also returned if the connection is successful.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const isRDP = rdp.IsRDP('acme.com', 3389);
// log(toJSON(isRDP));
// ```
func IsRDP(ctx context.Context, host string, port int) (IsRDPResponse, error) {
	executionId := ctx.Value("executionId").(string)
	return memoizedisRDP(ctx, executionId, host, port)
}

// @memo
func isRDP(ctx context.Context, executionId string, host string, port int) (IsRDPResponse, error) {
	resp := IsRDPResponse{}

	dialer := protocolstate.GetDialersWithId(executionId)
	if dialer == nil {
		return IsRDPResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
	}

	timeout := 5 * time.Second
	conn, err := dialer.Fastdialer.Dial(ctx, "tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		return resp, err
	}
	defer func() {
		_ = conn.Close()
	}()

	server, isRDP, err := rdp.DetectRDP(conn, timeout)
	if err != nil {
		return resp, err
	}
	if !isRDP {
		return resp, nil
	}
	resp.IsRDP = true
	resp.OS = server
	return resp, nil
}

type (
	// CheckRDPAuthResponse is the response from the CheckRDPAuth function.
	// this is returned by CheckRDPAuth function.
	// @example
	// ```javascript
	// const rdp = require('nuclei/rdp');
	// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
	// log(toJSON(checkRDPAuth));
	// ```
	CheckRDPAuthResponse struct {
		PluginInfo *plugins.ServiceRDP
		Auth       bool
	}
)

// CheckRDPAuth checks if the given host and port are running rdp server
// with authentication and returns their metadata.
// If connection is successful, it returns true.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const checkRDPAuth = rdp.CheckRDPAuth('acme.com', 3389);
// log(toJSON(checkRDPAuth));
// ```
func CheckRDPAuth(ctx context.Context, host string, port int) (CheckRDPAuthResponse, error) {
	executionId := ctx.Value("executionId").(string)
	return memoizedcheckRDPAuth(ctx, executionId, host, port)
}

// @memo
func checkRDPAuth(ctx context.Context, executionId string, host string, port int) (CheckRDPAuthResponse, error) {
	resp := CheckRDPAuthResponse{}

	dialer := protocolstate.GetDialersWithId(executionId)
	if dialer == nil {
		return CheckRDPAuthResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
	}
	timeout := 5 * time.Second
	conn, err := dialer.Fastdialer.Dial(ctx, "tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		return resp, err
	}
	defer func() {
		_ = conn.Close()
	}()

	pluginInfo, auth, err := rdp.DetectRDPAuth(conn, timeout)
	if err != nil {
		return resp, err
	}
	if !auth {
		return resp, nil
	}
	resp.Auth = true
	resp.PluginInfo = pluginInfo
	return resp, nil
}

type (
	SecurityLayer string
)

const (
	SecurityLayerNativeRDP                = "NativeRDP"
	SecurityLayerSSL                      = "SSL"
	SecurityLayerCredSSP                  = "CredSSP"
	SecurityLayerRDSTLS                   = "RDSTLS"
	SecurityLayerCredSSPWithEarlyUserAuth = "CredSSPWithEarlyUserAuth"
)

type (
	EncryptionLevel string
)

const (
	EncryptionLevelRC4_40bit  = "RC4_40bit"
	EncryptionLevelRC4_56bit  = "RC4_56bit"
	EncryptionLevelRC4_128bit = "RC4_128bit"
	EncryptionLevelFIPS140_1  = "FIPS140_1"
)

type (
	// RDPEncryptionResponse is the response from the CheckRDPEncryption function.
	// This is returned by CheckRDPEncryption function.
	// @example
	// ```javascript
	// const rdp = require('nuclei/rdp');
	// const encryption = rdp.CheckRDPEncryption('acme.com', 3389);
	// log(toJSON(encryption));
	// ```
	RDPEncryptionResponse struct {
		// Protocols
		NativeRDP                bool
		SSL                      bool
		CredSSP                  bool
		RDSTLS                   bool
		CredSSPWithEarlyUserAuth bool

		// EncryptionLevels
		RC4_40bit  bool
		RC4_56bit  bool
		RC4_128bit bool
		FIPS140_1  bool
	}
)

// CheckRDPEncryption checks the RDP server's supported security layers and encryption levels.
// It tests different protocols and ciphers to determine what is supported.
// @example
// ```javascript
// const rdp = require('nuclei/rdp');
// const encryption = rdp.CheckRDPEncryption('acme.com', 3389);
// log(toJSON(encryption));
// ```
func CheckRDPEncryption(ctx context.Context, host string, port int) (RDPEncryptionResponse, error) {
	executionId := ctx.Value("executionId").(string)
	return memoizedcheckRDPEncryption(ctx, executionId, host, port)
}

// @memo
func checkRDPEncryption(ctx context.Context, executionId string, host string, port int) (RDPEncryptionResponse, error) {
	dialer := protocolstate.GetDialersWithId(executionId)
	if dialer == nil {
		return RDPEncryptionResponse{}, fmt.Errorf("dialers not initialized for %s", executionId)
	}
	resp := RDPEncryptionResponse{}
	defaultTimeout := 5 * time.Second

	// Test different security protocols
	protocols := map[SecurityLayer]int{
		SecurityLayerNativeRDP:                0,
		SecurityLayerSSL:                      1,
		SecurityLayerCredSSP:                  3,
		SecurityLayerRDSTLS:                   4,
		SecurityLayerCredSSPWithEarlyUserAuth: 8,
	}

	for name, value := range protocols {
		dialCtx, cancel := context.WithTimeout(ctx, defaultTimeout)
		conn, err := dialer.Fastdialer.Dial(dialCtx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
		if err != nil {
			cancel()
			continue
		}
		if err := setConnDeadlineFromContext(conn, dialCtx); err != nil {
			_ = conn.Close()
			cancel()
			continue
		}

		// Test protocol
		isRDP, err := testRDPProtocol(conn, value)
		_ = conn.Close()
		cancel()
		if err == nil && isRDP {
			switch SecurityLayer(name) {
			case SecurityLayerNativeRDP:
				resp.NativeRDP = true
			case SecurityLayerSSL:
				resp.SSL = true
			case SecurityLayerCredSSP:
				resp.CredSSP = true
			case SecurityLayerRDSTLS:
				resp.RDSTLS = true
			case SecurityLayerCredSSPWithEarlyUserAuth:
				resp.CredSSPWithEarlyUserAuth = true
			}
		}
	}

	// Test different encryption levels
	ciphers := map[EncryptionLevel]int{
		EncryptionLevelRC4_40bit:  1,
		EncryptionLevelRC4_56bit:  8,
		EncryptionLevelRC4_128bit: 2,
		EncryptionLevelFIPS140_1:  16,
	}

	for encryptionLevel, value := range ciphers {
		dialCtx, cancel := context.WithTimeout(ctx, defaultTimeout)
		conn, err := dialer.Fastdialer.Dial(dialCtx, "tcp", net.JoinHostPort(host, strconv.Itoa(port)))
		if err != nil {
			cancel()
			continue
		}
		if err := setConnDeadlineFromContext(conn, dialCtx); err != nil {
			_ = conn.Close()
			cancel()
			continue
		}

		// Test cipher
		isRDP, err := testRDPCipher(conn, value)
		_ = conn.Close()
		cancel()
		if err == nil && isRDP {
			switch encryptionLevel {
			case EncryptionLevelRC4_40bit:
				resp.RC4_40bit = true
			case EncryptionLevelRC4_56bit:
				resp.RC4_56bit = true
			case EncryptionLevelRC4_128bit:
				resp.RC4_128bit = true
			case EncryptionLevelFIPS140_1:
				resp.FIPS140_1 = true
			}
		}
	}

	return resp, nil
}

func setConnDeadlineFromContext(conn net.Conn, ctx context.Context) error {
	deadline, ok := ctx.Deadline()
	if !ok {
		return nil
	}
	return conn.SetDeadline(deadline)
}

// testRDPProtocol tests RDP with a specific security protocol
func testRDPProtocol(conn net.Conn, protocol int) (bool, error) {
	// Send RDP connection request with specific protocol
	// This is a simplified version - in reality you'd need to implement the full RDP protocol
	// including the negotiation phase with the specified protocol
	_, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, byte(protocol), 0x00, 0x08, 0x00, 0x03, 0x00, 0x00, 0x00})
	if err != nil {
		return false, err
	}

	// Read response
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		return false, err
	}

	// Check if response indicates RDP
	if n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 {
		// For CredSSP and CredSSP with Early User Auth, we need to check for NLA support
		if protocol == 3 || protocol == 8 {
			// Check for NLA support in the response
			if n >= 19 && buf[18]&0x01 != 0 {
				return true, nil
			}
			return false, nil
		}
		return true, nil
	}

	return false, nil
}

// testRDPCipher tests RDP with a specific encryption level
func testRDPCipher(conn net.Conn, cipher int) (bool, error) {
	// Send RDP connection request with specific cipher
	// This is a simplified version - in reality you'd need to implement the full RDP protocol
	// including the negotiation phase with the specified cipher
	_, err := conn.Write([]byte{0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, byte(cipher), 0x03, 0x00, 0x00, 0x00})
	if err != nil {
		return false, err
	}

	// Read response
	buf := make([]byte, 1024)
	n, err := conn.Read(buf)
	if err != nil {
		return false, err
	}

	// Check if response indicates RDP
	if n >= 19 && buf[0] == 0x03 && buf[1] == 0x00 && buf[2] == 0x00 {
		// Check for encryption level support in the response
		if n >= 19 && buf[18]&byte(cipher) != 0 {
			return true, nil
		}
		return false, nil
	}

	return false, nil
}
