package proxy

import (
	"fmt"
	"net"
	"os"
	"regexp"
	"strconv"

	log "github.com/sirupsen/logrus"
	"github.com/xvzc/SpoofDPI/dns"
	"github.com/xvzc/SpoofDPI/packet"
	"github.com/xvzc/SpoofDPI/util"
)

type Proxy struct {
	addr           string
	port           int
	timeout        int
	resolver       *dns.DnsResolver
	windowSize     int
	allowedPattern *regexp.Regexp
	allowedUrls    *regexp.Regexp
}

func New(config *util.Config) *Proxy {
	return &Proxy{
		addr:           *config.Addr,
		port:           *config.Port,
		timeout:        *config.Timeout,
		windowSize:     *config.WindowSize,
		allowedPattern: config.AllowedPattern,
		allowedUrls:    config.AllowedUrls,
		resolver:       dns.NewResolver(config),
	}
}

func (pxy *Proxy) Start() {
	l, err := net.ListenTCP("tcp4", &net.TCPAddr{IP: net.ParseIP(pxy.addr), Port: pxy.port})
	if err != nil {
		log.Fatal("[PROXY] Error creating listener: ", err)
		os.Exit(1)
	}

  if pxy.timeout > 0 {
	  log.Println(fmt.Sprintf("[PROXY] Connection timeout is set to %dms", pxy.timeout))
  }

	log.Println("[PROXY] Created a listener on port", pxy.port)

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Fatal("[PROXY] Error accepting connection: ", err)
			continue
		}

		go func() {
			b, err := ReadBytes(conn.(*net.TCPConn))
			if err != nil {
				return
			}

			log.Debug("[PROXY] Request from ", conn.RemoteAddr(), "\n\n", string(b))

			pkt, err := packet.NewHttpPacket(b)
			if err != nil {
				log.Debug("[PROXY] Error while parsing request: ", string(b))
				conn.Close()
				return
			}

			if !pkt.IsValidMethod() {
				log.Debug("[PROXY] Unsupported method: ", pkt.Method())
				conn.Close()
				return
			}

			ip, err := pxy.resolver.Lookup(pkt.Domain())
			if err != nil {
        log.Debug("[PROXY] Error while dns lookup: ", pkt.Domain(), " ", err)
				conn.Write([]byte(pkt.Version() + " 502 Bad Gateway\r\n\r\n"))
				conn.Close()
				return
			}

			// Avoid recursively querying self
			if pkt.Port() == strconv.Itoa(pxy.port) && isLoopedRequest(net.ParseIP(ip)) {
				log.Error("[PROXY] Looped request has been detected. aborting.")
				conn.Close()
				return
			}

			if pkt.IsConnectMethod() {
				log.Debug("[PROXY] Start HTTPS")
				pxy.handleHttps(conn.(*net.TCPConn), pkt, ip)
			} else {
				log.Debug("[PROXY] Start HTTP")
				pxy.handleHttp(conn.(*net.TCPConn), pkt, ip)
			}
		}()
	}
}

func isLoopedRequest(ip net.IP) bool {
	// we don't handle IPv6 at all it seems
	if ip.To4() == nil {
		return false
	}

	if ip.IsLoopback() {
		return true
	}

	// Get list of available addresses
	// See `ip -4 addr show`
	addr, err := net.InterfaceAddrs() // needs AF_NETLINK on linux
	if err != nil {
		log.Error("[PROXY] Error while getting addresses of our network interfaces: ", err)
		return false
	}

	for _, addr := range addr {
		if ipnet, ok := addr.(*net.IPNet); ok {
			if ipnet.IP.To4() != nil && ipnet.IP.To4().Equal(ip) {
				return true
			}
		}
	}

	return false
}