package main

import (
	"context"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"server/torr/utils"

	"github.com/alexflint/go-arg"
	"github.com/pkg/browser"

	"server"
	"server/docs"
	"server/log"
	"server/settings"
	"server/torr"
	"server/version"
)

type args struct {
	Port        string `arg:"-p" help:"web server port (default 8090)"`
	IP          string `arg:"-i" help:"web server addr (default empty)"`
	Ssl         bool   `help:"enables https"`
	SslPort     string `help:"web server ssl port, If not set, will be set to default 8091 or taken from db(if stored previously). Accepted if --ssl enabled."`
	SslCert     string `help:"path to ssl cert file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."`
	SslKey      string `help:"path to ssl key file. If not set, will be taken from db(if stored previously) or default self-signed certificate/key will be generated. Accepted if --ssl enabled."`
	Path        string `arg:"-d" help:"database and config dir path"`
	LogPath     string `arg:"-l" help:"server log file path"`
	WebLogPath  string `arg:"-w" help:"web access log file path"`
	RDB         bool   `arg:"-r" help:"start in read-only DB mode"`
	HttpAuth    bool   `arg:"-a" help:"enable http auth on all requests"`
	DontKill    bool   `arg:"-k" help:"don't kill server on signal"`
	UI          bool   `arg:"-u" help:"open torrserver page in browser"`
	TorrentsDir string `arg:"-t" help:"autoload torrents from dir"`
	TorrentAddr string `help:"Torrent client address, like 127.0.0.1:1337 (default :PeersListenPort)"`
	PubIPv4     string `arg:"-4" help:"set public IPv4 addr"`
	PubIPv6     string `arg:"-6" help:"set public IPv6 addr"`
	SearchWA    bool   `arg:"-s" help:"search without auth"`
	MaxSize     string `arg:"-m" help:"max allowed stream size (in Bytes)"`
	TGToken     string `arg:"-T" help:"telegram bot token"`
	FusePath    string `arg:"-f" help:"fuse mount path"`
	WebDAV      bool   `help:"web dav enable"`
	ProxyURL    string `help:"proxy URL for BitTorrent traffic (http, socks4, socks5, socks5h), e.g. socks5://user:password@127.0.0.1:8080"`
	ProxyMode   string `help:"proxy mode: tracker (only HTTP trackers, default), peers (only peer connections), or full (all traffic)"`
	ForceHTTPS  bool   `arg:"--force-https" help:"redirect all HTTP requests to HTTPS (requires --ssl)"`
}

func (args) Version() string {
	return "TorrServer " + version.Version
}

var params args

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	arg.MustParse(&params)

	if params.Path == "" {
		params.Path, _ = os.Getwd()
	}

	if params.Port == "" {
		params.Port = "8090"
	}

	settings.Path = params.Path
	settings.HttpAuth = params.HttpAuth
	log.Init(params.LogPath, params.WebLogPath)

	fmt.Println("=========== START ===========")
	fmt.Println("TorrServer", version.Version+",", runtime.Version()+",", "CPU Num:", runtime.NumCPU())
	if params.HttpAuth {
		log.TLogln("Use HTTP Auth file", settings.Path+"/accs.db")
	}
	if params.RDB {
		log.TLogln("Running in Read-only DB mode!")
	}
	docs.SwaggerInfo.Version = version.Version

	// Simple Usage:
	dnsResolve()

	// Advanced Usage:
	// config := DNSConfig{
	//     PrimaryServers: []string{"1.1.1.1:53", "8.8.8.8:53"},
	//     Timeout:        3 * time.Second,
	// }
	// checker := NewDNSChecker(config)
	// // Perform DNS lookup with automatic fallback
	// addrs, err := checker.LookupHostWithFallback("themoviedb.org")
	// if err != nil {
	//     log.TLogln("DNS lookup failed:", err)
	// } else {
	// 	fmt.Println("DNS resolved:", addrs)
	// }

	Preconfig(params.DontKill)

	if params.UI {
		go func() {
			time.Sleep(time.Second)
			if params.Ssl {
				browser.OpenURL("https://127.0.0.1:" + params.SslPort)
			} else {
				browser.OpenURL("http://127.0.0.1:" + params.Port)
			}
		}()
	}

	if params.TorrentAddr != "" {
		settings.TorAddr = params.TorrentAddr
	}

	if params.PubIPv4 != "" {
		settings.PubIPv4 = params.PubIPv4
	}

	if params.PubIPv6 != "" {
		settings.PubIPv6 = params.PubIPv6
	}

	if params.TorrentsDir != "" {
		go watchTDir(params.TorrentsDir)
	}

	if params.MaxSize != "" {
		maxSize, err := strconv.ParseInt(params.MaxSize, 10, 64)
		if err == nil {
			settings.MaxSize = maxSize
		}
	}

	if params.ProxyURL != "" && params.ProxyMode == "" {
		params.ProxyMode = "tracker" // default
	}
	if params.ProxyMode != "" && params.ProxyMode != "tracker" && params.ProxyMode != "peers" && params.ProxyMode != "full" {
		log.TLogln("Invalid proxy mode, using default 'tracker'")
		params.ProxyMode = "tracker"
	}

	settings.Args = &settings.ExecArgs{
		Port:        params.Port,
		IP:          params.IP,
		Ssl:         params.Ssl,
		SslPort:     params.SslPort,
		SslCert:     params.SslCert,
		SslKey:      params.SslKey,
		Path:        params.Path,
		LogPath:     params.LogPath,
		WebLogPath:  params.WebLogPath,
		RDB:         params.RDB,
		HttpAuth:    params.HttpAuth,
		DontKill:    params.DontKill,
		UI:          params.UI,
		TorrentsDir: params.TorrentsDir,
		TorrentAddr: params.TorrentAddr,
		PubIPv4:     params.PubIPv4,
		PubIPv6:     params.PubIPv6,
		SearchWA:    params.SearchWA,
		MaxSize:     params.MaxSize,
		TGToken:     params.TGToken,
		FusePath:    params.FusePath,
		WebDAV:      params.WebDAV,
		ProxyURL:    params.ProxyURL,
		ProxyMode:   params.ProxyMode,
		ForceHTTPS:  params.ForceHTTPS,
	}

	if params.ProxyURL != "" {
		log.TLogln("Proxy configured from CLI:", params.ProxyURL, "mode:", settings.Args.ProxyMode)
	}

	if params.ForceHTTPS && !params.Ssl {
		log.TLogln("Error: --force-https requires --ssl")
		os.Exit(1)
	}

	server.Start()
	log.TLogln(server.WaitServer())
	log.Close()
	time.Sleep(time.Second * 3)
	os.Exit(0)
}

func watchTDir(dir string) {
	time.Sleep(5 * time.Second)
	path, err := filepath.Abs(dir)
	if err != nil {
		path = dir
	}
	for {
		files, err := os.ReadDir(path)
		if err == nil {
			for _, file := range files {
				filename := filepath.Join(path, file.Name())
				if strings.ToLower(filepath.Ext(file.Name())) == ".torrent" {
					sp, err := utils.OpenTorrentFile(filename)
					if err == nil {
						tor, err := torr.AddTorrent(sp, "", "", "", "")
						if err == nil {
							if tor.GotInfo() {
								if tor.Title == "" {
									tor.Title = tor.Name()
								}
								torr.SaveTorrentToDB(tor)
								tor.Drop()
								os.Remove(filename)
								time.Sleep(time.Second)
							} else {
								log.TLogln("Error get info from torrent")
							}
						} else {
							log.TLogln("Error parse torrent file:", err)
						}
					} else {
						log.TLogln("Error parse file name:", err)
					}
				}
			}
		} else {
			log.TLogln("Error read dir:", err)
		}
		time.Sleep(time.Second * 5)
	}
}

///////////
/// DNS
///

// DNSConfig holds DNS resolver configuration
type DNSConfig struct {
	PrimaryServers  []string
	FallbackServers []string
	Timeout         time.Duration
	CacheDuration   time.Duration
}

// DefaultDNSConfig returns a sensible default configuration
func DefaultDNSConfig() DNSConfig {
	return DNSConfig{
		PrimaryServers: []string{
			"8.8.8.8:53", // Google DNS
			"1.1.1.1:53", // CloudFlare DNS
			"9.9.9.9:53", // Quad9 DNS
		},
		FallbackServers: []string{
			"208.67.222.222:53", // OpenDNS
			"64.6.64.6:53",      // Verisign
		},
		Timeout:       5 * time.Second,
		CacheDuration: 5 * time.Minute,
	}
}

// DNSChecker manages DNS resolution with fallback support
type DNSChecker struct {
	config         DNSConfig
	customResolver *net.Resolver
	cache          map[string][]string
	cacheTime      map[string]time.Time
	mu             sync.RWMutex
	useFallback    bool
}

// NewDNSChecker creates a new DNS checker instance
func NewDNSChecker(config DNSConfig) *DNSChecker {
	if len(config.PrimaryServers) == 0 {
		config = DefaultDNSConfig()
	}

	return &DNSChecker{
		config:    config,
		cache:     make(map[string][]string),
		cacheTime: make(map[string]time.Time),
	}
}

// CheckAndResolve performs DNS check and returns a resolver
func (d *DNSChecker) CheckAndResolve() *net.Resolver {
	// Test system DNS first
	if d.testSystemDNS() {
		log.TLogln("System DNS check passed")
		return net.DefaultResolver
	}

	log.TLogln("System DNS check failed, using custom resolver")
	d.initCustomResolver()
	return d.customResolver
}

// testSystemDNS checks if system DNS is working properly
func (d *DNSChecker) testSystemDNS() bool {
	_, cancel := context.WithTimeout(context.Background(), d.config.Timeout)
	defer cancel()

	addrs, err := net.LookupHost("themoviedb.org")
	if err != nil {
		log.TLogln("DNS lookup error:", err)
		return false
	}

	if len(addrs) == 0 {
		log.TLogln("DNS lookup returned no addresses")
		return false
	}

	// Check for suspicious addresses (DNS hijacking/pollution)
	for _, addr := range addrs {
		if isSuspiciousAddress(addr) {
			log.TLogln("Suspicious DNS address detected:", addr)
			return false
		}
	}

	return true
}

// isSuspiciousAddress checks if an address indicates DNS issues
func isSuspiciousAddress(addr string) bool {
	suspiciousPrefixes := []string{
		"127.0.0.1", // Localhost
		"0.0.0.0",   // Invalid address
		"::1",       // IPv6 localhost
		// "10.",       // Private network
		"192.168.", // Private network
		"169.254.", // Link-local
		// "172.16.", "172.17.", "172.18.", "172.19.",
		// "172.20.", "172.21.", "172.22.", "172.23.",
		// "172.24.", "172.25.", "172.26.", "172.27.",
		// "172.28.", "172.29.", "172.30.", "172.31.", // Private network range
	}

	for _, prefix := range suspiciousPrefixes {
		if strings.HasPrefix(addr, prefix) {
			return true
		}
	}

	return false
}

// initCustomResolver creates a custom resolver with fallback support
func (d *DNSChecker) initCustomResolver() {
	d.customResolver = &net.Resolver{
		PreferGo: true, // Use Go's DNS implementation
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			dialer := &net.Dialer{
				Timeout:   d.config.Timeout,
				KeepAlive: 30 * time.Second,
			}

			// Try primary servers first
			for _, dns := range d.config.PrimaryServers {
				conn, err := dialer.DialContext(ctx, network, dns)
				if err == nil {
					return conn, nil
				}
				log.TLogln("Failed to connect to DNS server", dns, ":", err)
			}

			// Try fallback servers if primary fails
			for _, dns := range d.config.FallbackServers {
				conn, err := dialer.DialContext(ctx, network, dns)
				if err == nil {
					log.TLogln("Using fallback DNS server:", dns)
					return conn, nil
				}
				log.TLogln("Failed to connect to fallback DNS", dns, ":", err)
			}

			return nil, fmt.Errorf("all DNS servers failed")
		},
	}

	d.useFallback = true
}

// LookupHostWithFallback performs DNS lookup with automatic fallback
func (d *DNSChecker) LookupHostWithFallback(host string) ([]string, error) {
	// Check cache first
	if addrs, ok := d.getFromCache(host); ok {
		return addrs, nil
	}

	// Use appropriate resolver
	var resolver *net.Resolver
	if d.useFallback && d.customResolver != nil {
		resolver = d.customResolver
	} else {
		resolver = net.DefaultResolver
	}

	// Perform lookup with timeout
	ctx, cancel := context.WithTimeout(context.Background(), d.config.Timeout)
	defer cancel()

	addrs, err := resolver.LookupHost(ctx, host)
	if err != nil {
		// If using system DNS fails, try custom resolver
		if !d.useFallback && d.customResolver != nil {
			log.TLogln("System DNS failed, trying custom resolver")
			addrs, err = d.customResolver.LookupHost(ctx, host)
		}
	}

	// Cache successful results
	if err == nil && len(addrs) > 0 {
		d.addToCache(host, addrs)
	}

	return addrs, err
}

// getFromCache retrieves DNS results from cache
func (d *DNSChecker) getFromCache(host string) ([]string, bool) {
	d.mu.RLock()
	defer d.mu.RUnlock()

	if addrs, ok := d.cache[host]; ok {
		if time.Since(d.cacheTime[host]) < d.config.CacheDuration {
			return addrs, true
		}
		// Expired, remove from cache
		delete(d.cache, host)
		delete(d.cacheTime, host)
	}

	return nil, false
}

// addToCache adds DNS results to cache
func (d *DNSChecker) addToCache(host string, addrs []string) {
	d.mu.Lock()
	defer d.mu.Unlock()

	d.cache[host] = addrs
	d.cacheTime[host] = time.Now()
}

// Simple usage function (backward compatible)
func dnsResolve() {
	checker := NewDNSChecker(DefaultDNSConfig())
	resolver := checker.CheckAndResolve()

	// Store the resolver for later use if needed
	net.DefaultResolver = resolver // Optional: replace global resolver

	// Test the resolver
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	addrs, err := resolver.LookupHost(ctx, "themoviedb.org")
	if err != nil {
		log.TLogln("DNS resolution test failed:", err)
	} else {
		log.TLogln("DNS resolution successful, addresses:", addrs)
	}
}

// func dnsResolve() {
// 	addrs, err := net.LookupHost("themoviedb.org")
// 	if len(addrs) == 0 {
// 		log.TLogln("System DNS check failed", err)

// 		fn := func(ctx context.Context, network, address string) (net.Conn, error) {
// 			d := net.Dialer{}
// 			return d.DialContext(ctx, "udp", "1.1.1.1:53")
// 		}

// 		net.DefaultResolver = &net.Resolver{
// 			Dial: fn,
// 		}

// 		addrs, err = net.LookupHost("themoviedb.org")
// 		if err != nil {
// 			log.TLogln("Check CloudFlare DNS error:", err)
// 		} else {
// 			log.TLogln("Use CloudFlare DNS")
// 		}
// 	} else {
// 		log.TLogln("System DNS check passed")
// 	}
// }
