package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"flag"
	"net"
	"os"
	"strings"

	"github.com/dalbodeule/hop-gate/internal/config"
	"github.com/dalbodeule/hop-gate/internal/dtls"
	"github.com/dalbodeule/hop-gate/internal/logging"
	"github.com/dalbodeule/hop-gate/internal/proxy"
)

// maskAPIKey 는 로그에 노출할 때 클라이언트 API Key 를 일부만 보여주기 위한 헬퍼입니다.
func maskAPIKey(key string) string {
	if len(key) <= 8 {
		return "***"
	}
	return key[:4] + "..." + key[len(key)-4:]
}

// firstNonEmpty 는 앞에서부터 처음으로 non-empty 인 문자열을 반환합니다.
func firstNonEmpty(values ...string) string {
	for _, v := range values {
		if strings.TrimSpace(v) != "" {
			return v
		}
	}
	return ""
}

func main() {
	logger := logging.NewStdJSONLogger("client")

	// CLI 인자 정의 (env 보다 우선 적용됨)
	serverAddrFlag := flag.String("server-addr", "", "DTLS server address (host:port)")
	domainFlag := flag.String("domain", "", "registered domain (e.g. api.example.com)")
	apiKeyFlag := flag.String("api-key", "", "client API key for the domain (64 chars)")
	localTargetFlag := flag.String("local-target", "", "local HTTP target (host:port), e.g. 127.0.0.1:8080")

	flag.Parse()

	// 1. 환경변수(.env 포함)에서 클라이언트 설정 로드
	envCfg, err := config.LoadClientConfigFromEnv()
	if err != nil {
		logger.Error("failed to load client config from env", logging.Fields{
			"error": err.Error(),
		})
		os.Exit(1)
	}

	// 2. CLI 인자 우선, env 후순위로 최종 설정 구성
	finalCfg := &config.ClientConfig{
		ServerAddr:   firstNonEmpty(strings.TrimSpace(*serverAddrFlag), strings.TrimSpace(envCfg.ServerAddr)),
		Domain:       firstNonEmpty(strings.TrimSpace(*domainFlag), strings.TrimSpace(envCfg.Domain)),
		ClientAPIKey: firstNonEmpty(strings.TrimSpace(*apiKeyFlag), strings.TrimSpace(envCfg.ClientAPIKey)),
		LocalTarget:  firstNonEmpty(strings.TrimSpace(*localTargetFlag), strings.TrimSpace(envCfg.LocalTarget)),
		Debug:        envCfg.Debug,
		Logging:      envCfg.Logging,
	}

	// 3. 필수 필드 검증
	missing := []string{}
	if finalCfg.ServerAddr == "" {
		missing = append(missing, "server_addr")
	}
	if finalCfg.Domain == "" {
		missing = append(missing, "domain")
	}
	if finalCfg.ClientAPIKey == "" {
		missing = append(missing, "api_key")
	}
	if finalCfg.LocalTarget == "" {
		missing = append(missing, "local_target")
	}

	if len(missing) > 0 {
		logger.Error("client config missing required fields", logging.Fields{
			"missing": missing,
		})
		os.Exit(1)
	}

	logger.Info("hop-gate client starting", logging.Fields{
		"stack":                 "prometheus-loki-grafana",
		"server_addr":           finalCfg.ServerAddr,
		"domain":                finalCfg.Domain,
		"local_target":          finalCfg.LocalTarget,
		"client_api_key_masked": maskAPIKey(finalCfg.ClientAPIKey),
		"debug":                 finalCfg.Debug,
	})

	// 4. DTLS 클라이언트 연결 및 핸드셰이크
	ctx := context.Background()

	// 디버그 모드에서는 서버 인증서 검증을 스킵(InsecureSkipVerify=true) 하여
	// self-signed 테스트 인증서도 신뢰하도록 합니다.
	// 운영 환경에서는 Debug=false 로 두고, 올바른 RootCAs / ServerName 을 갖는 tls.Config 를 사용해야 합니다.
	var tlsCfg *tls.Config
	if finalCfg.Debug {
		tlsCfg = &tls.Config{
			InsecureSkipVerify: true,
			MinVersion:         tls.VersionTLS12,
		}
	} else {
		// 운영 모드: 시스템 루트 CA + SNI(ServerName)에 서버 도메인 설정
		rootCAs, err := x509.SystemCertPool()
		if err != nil || rootCAs == nil {
			rootCAs = x509.NewCertPool()
		}
		tlsCfg = &tls.Config{
			RootCAs:    rootCAs,
			MinVersion: tls.VersionTLS12,
		}
	}
	// DTLS 서버 측은 SNI(ServerName)가 HOP_SERVER_DOMAIN(cfg.Domain)과 일치하는지 검사하므로,
	// 클라이언트 TLS 설정에도 반드시 도메인을 설정해준다.
	//
	// finalCfg.ServerAddr 가 "host:port" 형태이므로, SNI 에는 DNS(host) 부분만 넣어야 한다.
	host := finalCfg.ServerAddr
	if h, _, err := net.SplitHostPort(finalCfg.ServerAddr); err == nil && strings.TrimSpace(h) != "" {
		host = h
	}
	tlsCfg.ServerName = host

	client := dtls.NewPionClient(dtls.PionClientConfig{
		Addr:      finalCfg.ServerAddr,
		TLSConfig: tlsCfg,
	})

	sess, err := client.Connect()
	if err != nil {
		logger.Error("failed to establish dtls session", logging.Fields{
			"error": err.Error(),
		})
		os.Exit(1)
	}
	defer sess.Close()

	hsRes, err := dtls.PerformClientHandshake(ctx, sess, logger, finalCfg.Domain, finalCfg.ClientAPIKey, finalCfg.LocalTarget)
	if err != nil {
		logger.Error("dtls handshake failed", logging.Fields{
			"error": err.Error(),
		})
		os.Exit(1)
	}

	logger.Info("dtls handshake completed", logging.Fields{
		"domain":       hsRes.Domain,
		"local_target": finalCfg.LocalTarget,
	})

	// 5. DTLS 세션 위에서 서버 요청을 처리하는 클라이언트 프록시 루프 시작
	clientProxy := proxy.NewClientProxy(logger, finalCfg.LocalTarget)
	logger.Info("starting client proxy loop", logging.Fields{
		"local_target": finalCfg.LocalTarget,
	})

	if err := clientProxy.StartLoop(ctx, sess); err != nil {
		logger.Error("client proxy loop exited with error", logging.Fields{
			"error": err.Error(),
		})
		os.Exit(1)
	}

	logger.Info("client proxy loop exited normally", nil)
}
