package m9zTtyPwd

import (
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	g "tgk-touch/internal/global"
	"time"

	"github.com/towgo/towgo/lib/system"
	"github.com/towgo/towgo/towgo"
)

const (
	DefaultPassword      = "123456"
	DefaultSalt          = "m9zTty"
	SessionExpireMinutes = 10
)

var (
	manager *PasswordManager = &PasswordManager{
		verifiedSessions: make(map[string]time.Time),
	}
	passwordEnabled bool
)

var noInterceptor bool

var (
	whiteListMethods = map[string]bool{
		"/m9z/password/verify": true,
		"/m9z/password/change": true,
		//"/m9z/password/enable":  true,
		//"/m9z/password/disable": true,
		"/m9z/password/status": true,
		//	实时监控
		"/m9z/touchdisplay/getDeviceID": true,
		"/m9z/getDeviceStatus2":         true,
		"/getMessageInterval":           true,
		"/adl400/readPhaseData":         true,
		// 报警预览
		"/m9z/fault/list": true,
	}
	whiteListPrefixes = []string{
		//"/m9z/get",
		//"/m9z/system/get",
		//"/m9z/fault",
	}
)

type PasswordManager struct {
	mu               sync.RWMutex
	hashedPwd        string
	salt             string
	enabledState     int
	verifiedSessions map[string]time.Time
}

type PasswordData struct {
	HashedPassword string `json:"hashed_password"`
	Salt           string `json:"salt"`
	Enabled        int    `json:"enabled"`
}

func getPwdFilePath() string {
	return filepath.Join(system.GetPathOfProgram(), "data", "m9zTtyPwd.json")
}

func hashPassword(pwd, salt string) string {
	data := []byte(pwd + salt)
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:])
}

func (m *PasswordManager) loadFromFile() error {
	m.mu.Lock()
	defer m.mu.Unlock()
	filePath := getPwdFilePath()
	data, err := os.ReadFile(filePath)

	if err != nil {
		if os.IsNotExist(err) {

			m.hashedPwd = hashPassword(DefaultPassword, DefaultSalt)
			m.salt = DefaultSalt

			return m.saveToFile()
		}
		return err
	}

	var pwdData PasswordData
	if err := json.Unmarshal(data, &pwdData); err != nil {
		return err
	}

	m.hashedPwd = pwdData.HashedPassword
	m.salt = pwdData.Salt
	m.enabledState = pwdData.Enabled
	switch pwdData.Enabled {
	case 1:
		passwordEnabled = true
	case 2:
		passwordEnabled = false
	default:
		passwordEnabled = g.Config().Password.Enabled
		m.enabledState = 0
	}
	return nil
}

func (m *PasswordManager) saveToFile() error {
	filePath := getPwdFilePath()
	dir := filepath.Dir(filePath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return err
	}

	var enabledState int
	switch {
	case passwordEnabled:
		enabledState = 1
	case !passwordEnabled && m.enabledState != 0:
		enabledState = 2
	default:
		enabledState = m.enabledState
	}

	pwdData := PasswordData{
		HashedPassword: m.hashedPwd,
		Salt:           m.salt,
		Enabled:        enabledState,
	}

	data, err := json.Marshal(pwdData)
	if err != nil {
		return err
	}

	return os.WriteFile(filePath, data, 0600)
}

func (m *PasswordManager) verify(pwd string) bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return m.hashedPwd == hashPassword(pwd, m.salt)
}

func (m *PasswordManager) enabled() bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	return passwordEnabled
}

func (m *PasswordManager) setEnabled(enabled bool) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	passwordEnabled = enabled
	if enabled {
		m.enabledState = 1
	} else {
		m.enabledState = 2
	}
	return m.saveToFile()
}

func (m *PasswordManager) change(newPwd string) error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.hashedPwd = hashPassword(newPwd, m.salt)
	return m.saveToFile()
}

func (m *PasswordManager) addVerifiedSession(sessionId string) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.verifiedSessions[sessionId] = time.Now().Add(SessionExpireMinutes * time.Minute)
}

func (m *PasswordManager) isSessionVerified(sessionId string) bool {
	m.mu.RLock()
	defer m.mu.RUnlock()
	expireTime, ok := m.verifiedSessions[sessionId]
	if !ok {
		return false
	}
	if time.Now().After(expireTime) {
		return false
	}
	return true
}

func (m *PasswordManager) removeSession(sessionId string) {
	m.mu.Lock()
	defer m.mu.Unlock()
	delete(m.verifiedSessions, sessionId)
}

func (m *PasswordManager) cleanupExpiredSessions() {
	m.mu.Lock()
	defer m.mu.Unlock()
	now := time.Now()
	for sessionId, expireTime := range m.verifiedSessions {
		if now.After(expireTime) {
			delete(m.verifiedSessions, sessionId)
		}
	}
}

func (m *PasswordManager) isWhiteListed(method string) bool {
	if whiteListMethods[method] {
		return true
	}
	for _, prefix := range whiteListPrefixes {
		if strings.HasPrefix(method, prefix) {
			return true
		}
	}
	return false
}

func NoInterceptor(b bool) {
	noInterceptor = b
}

func UnauthorizedMethodAdd(method string) {
	whiteListMethods[method] = true
}

func UnauthorizedMethodPrefixAdd(prefix string) {
	whiteListPrefixes = append(whiteListPrefixes, prefix)
}

var mu sync.Mutex

func Init() {
	mu.Lock()
	defer mu.Unlock()

	if err := manager.loadFromFile(); err != nil {
		g.GVA_LOG.Sugar().Warnf("load password data failed: %v, using default", err)
		manager.hashedPwd = hashPassword(DefaultPassword, DefaultSalt)
		manager.salt = DefaultSalt
		manager.enabledState = 0
		manager.saveToFile()
	}

	towgo.SetFunc("/m9z/password/verify", verifyPwd)
	towgo.SetFunc("/m9z/password/change", changePwd)
	towgo.SetFunc("/m9z/password/enable", enablePassword)
	towgo.SetFunc("/m9z/password/disable", disablePassword)
	towgo.SetFunc("/m9z/password/status", getPasswordStatus)
	towgo.SetFunc("/m9z/system/getTime", getSystemTime)
	towgo.SetFunc("/m9z/system/setTime", setSystemTime)

	towgo.AddInterceptor(pwdInterceptor)

	go func() {
		ticker := time.NewTicker(5 * time.Minute)
		for range ticker.C {
			manager.cleanupExpiredSessions()
		}
	}()
}

func generateSessionId() string {
	data := []byte(time.Now().String())
	sum := md5.Sum(data)
	return hex.EncodeToString(sum[:])
}

func verifyPwd(rpcConn towgo.JsonRpcConnection) {
	var req struct {
		Password string `json:"password"`
	}
	rpcConn.ReadParams(&req)

	sessionId := generateSessionId()
	if manager.verify(req.Password) {
		manager.addVerifiedSession(sessionId)
		rpcConn.WriteResult(map[string]interface{}{
			"success":    true,
			"session_id": sessionId,
			"message":    "verified",
		})
	} else {
		rpcConn.WriteError(401, "invalid password")
	}
}

type ChangePwdReq struct {
	OldPassword string `json:"old_password"`
	NewPassword string `json:"new_password"`
}

func changePwd(rpcConn towgo.JsonRpcConnection) {
	var req ChangePwdReq
	rpcConn.ReadParams(&req)

	if !manager.verify(req.OldPassword) {
		rpcConn.WriteError(500, "旧密码错误")
		return
	}

	if len(req.NewPassword) < 6 {
		rpcConn.WriteError(500, "新密码长度小于 6")
		return
	}

	if err := manager.change(req.NewPassword); err != nil {
		rpcConn.WriteError(500, "change password failed: "+err.Error())
		return
	}

	rpcConn.WriteResult(map[string]interface{}{
		"success": true,
		"message": "password changed",
	})
}

func pwdInterceptor(conn towgo.JsonRpcConnection) error {
	if noInterceptor || !passwordEnabled {
		return nil
	}

	rpcRequest := conn.GetRpcRequest()
	rpcResponse := conn.GetRpcResponse()

	if manager.isWhiteListed(rpcRequest.Method) {
		return nil
	}

	sessionId := rpcRequest.Session
	if sessionId == "" {
		rpcResponse.Error.Set(401, "password verification required")
		conn.Write()
		return errors.New("password verification required")
	}

	if !manager.isSessionVerified(sessionId) {
		rpcResponse.Error.Set(401, "session expired or invalid")
		conn.Write()
		return errors.New("password verification required")
	}

	return nil
}

func getSystemTime(rpcConn towgo.JsonRpcConnection) {
	now := time.Now()
	rpcConn.WriteResult(map[string]interface{}{
		"timestamp": now.Unix(),
		"datetime":  now.Format("2006-01-02 15:04:05"),
		"timezone":  now.Location().String(),
	})
}

type SetTimeReq struct {
	Datetime string `json:"datetime"`
}

func setSystemTime(rpcConn towgo.JsonRpcConnection) {
	var req SetTimeReq
	rpcConn.ReadParams(&req)

	if req.Datetime == "" {
		rpcConn.WriteError(400, "datetime is required")
		return
	}

	layouts := []string{
		"2006-01-02 15:04:05",
		"2006-01-02T15:04:05Z",
		"2006-01-02 15:04",
		"2006-01-02",
	}

	var targetTime time.Time
	var parseErr error
	for _, layout := range layouts {
		targetTime, parseErr = time.Parse(layout, req.Datetime)
		if parseErr == nil {
			break
		}
	}

	if parseErr != nil {
		rpcConn.WriteError(400, "invalid datetime format, supported: 2006-01-02 15:04:05, 2006-01-02T15:04:05Z, 2006-01-02 15:04, 2006-01-02")
		return
	}

	unixTime := targetTime.Unix()
	cmd := exec.Command("date", "-s", targetTime.Format("2006-01-02 15:04:05"))
	cmd.Run()

	cmd = exec.Command("hwclock", "-w")
	cmd.Run()

	cmd = exec.Command("timedatectl", "set-ntp", "false")
	cmd.Run()

	rpcConn.WriteResult(map[string]interface{}{
		"success":   true,
		"message":   "system time updated",
		"old_time":  time.Now().Format("2006-01-02 15:04:05"),
		"new_time":  targetTime.Format("2006-01-02 15:04:05"),
		"unix_time": unixTime,
	})
}

func enablePassword(rpcConn towgo.JsonRpcConnection) {
	if err := manager.setEnabled(true); err != nil {
		rpcConn.WriteError(500, "save failed: "+err.Error())
		return
	}
	rpcConn.WriteResult(map[string]interface{}{
		"success": true,
		"message": "password enabled",
	})
}

func disablePassword(rpcConn towgo.JsonRpcConnection) {
	if err := manager.setEnabled(false); err != nil {
		rpcConn.WriteError(500, "save failed: "+err.Error())
		return
	}
	rpcConn.WriteResult(map[string]interface{}{
		"success": true,
		"message": "password disabled",
	})
}

func getPasswordStatus(rpcConn towgo.JsonRpcConnection) {
	rpcConn.WriteResult(map[string]interface{}{
		"enabled": passwordEnabled,
	})
}
