package statistic

import (
	"errors"

	"github.com/Dreamacro/clash/common/snifer/tls"
	"github.com/Dreamacro/clash/component/resolver"
	C "github.com/Dreamacro/clash/constant"
	"github.com/Dreamacro/clash/log"

	"go.uber.org/atomic"
)

type sniffing struct {
	C.Conn

	metadata   *C.Metadata
	totalWrite *atomic.Uint64
	allowBreak bool
}

func (r *sniffing) Read(b []byte) (int, error) {
	return r.Conn.Read(b)
}

func (r *sniffing) Write(b []byte) (int, error) {
	if r.totalWrite.Load() < 128 && r.metadata.Host == "" && (r.metadata.DstPort == "443" || r.metadata.DstPort == "8443" || r.metadata.DstPort == "993" || r.metadata.DstPort == "465" || r.metadata.DstPort == "995") {
		header, err := tls.SniffTLS(b)
		if err == nil {
			resolver.InsertHostByIP(r.metadata.DstIP, header.Domain())
			log.Debugln("[SNIFFER] use sni update host: %s ip: %s", header.Domain(), r.metadata.DstIP.String())
			if r.allowBreak {
				_ = r.Conn.Close()
				return 0, errors.New("sni update, break current link to avoid leaks")
			} else {
				r.metadata.Host = header.Domain()
			}
		}
	}

	n, err := r.Conn.Write(b)
	r.totalWrite.Add(uint64(n))

	return n, err
}

func (r *sniffing) Close() error {
	return r.Conn.Close()
}

func NewSniffing(conn C.Conn, metadata *C.Metadata, rule C.Rule) C.Conn {
	return &sniffing{
		Conn:       conn,
		metadata:   metadata,
		totalWrite: atomic.NewUint64(0),
		allowBreak: rule != nil,
	}
}
