package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"time"

	"golang.org/x/text/encoding/korean"
	"golang.org/x/text/transform"
	"gopkg.in/yaml.v2"
)

// Config 구조체 정의
type Config struct {
	CallNo            string `yaml:"call_no"`
	ServerAddress     string `yaml:"server_address"`
	MaxRetries        int    `yaml:"max_retries"`
	ReconnectInterval int    `yaml:"reconnect_interval"`
	ReadTimeout       int    `yaml:"read_timeout"`
	RestAPIEndpoint   string `yaml:"rest_api_endpoint"`
	RestAPIGetDevice  string `yaml:"rest_api_get_device"`
}

// LogEntry 구조체 정의
type LogEntry struct {
	Size     byte
	Type     uint8
	DateTime string
	On       uint8
	Address  [7]byte
	Area     [24]byte
	Device   [24]byte
}

// JSONLogEntry 구조체 정의
type JSONLogEntry struct {
	CallNo   string `json:"callNo"`
	Type     uint8  `json:"type"`
	DateTime string `json:"dateTime"`
	Status   string `json:"status"`
	Address  string `json:"addr"`
	Area     string `json:"zoneNm"`
	Device   string `json:"deviceNm"`
}

type DeviceInfo struct {
	SeqNo     int    `json:"seq"`
	OrdNo     int    `json:"ordNo"`
	SerialKey string `json:"serial_key"`
	CallNo    string `json:"call_no"`
	DLCIP     string `json:"dlc_ip"`
	RecIP     string `json:"rec_ip"`
	// DLCNetmask string `json:"dlc_netmask"`
	// DLCGateway string `json:"dlc_gateway"`
}

var config Config

func loadConfig() error {
	data, err := ioutil.ReadFile("config.yaml")
	if err != nil {
		return err
	}

	err = yaml.Unmarshal(data, &config)
	if err != nil {
		return err
	}

	return nil
}

func main() {

	fmt.Println("--------------------------------------")
	fmt.Println(" LTE Alarm System : Version 1.0.2")
	fmt.Println("--------------------------------------")

	if err := loadConfig(); err != nil {
		fmt.Printf("Error loading config: %v\n", err)
		os.Exit(1)
	}

	fmt.Println(config)

	// HTTP 서버 설정
	http.HandleFunc("/update-phone", updatePhoneNumberHandler)
	go func() {
		fmt.Println("Starting HTTP server on :5119")
		if err := http.ListenAndServe(":5119", nil); err != nil {
			fmt.Printf("HTTP server error: %v\n", err)
		}
	}()

	// 장치 정보 가져오기 및 네트워크 설정
	maxRetries := 5
	for i := 0; i < maxRetries; i++ {
		deviceInfo, err := getDeviceInfo(config.CallNo) // config.CallNo는 장치의 고유 번호입니다.
		if err != nil {
			fmt.Printf("Error getting device info: %v\n", err)
			if i == maxRetries-1 {
				rebootSystem()
			}
			time.Sleep(1 * time.Minute)
			continue
		}

		setServerGPIO()

		err = setNetworkConfig(deviceInfo)
		if err != nil {
			fmt.Printf("Error setting network config: %v\n", err)
			if i == maxRetries-1 {
				rebootSystem()
			}
			time.Sleep(1 * time.Minute)
			continue
		}

		// 성공적으로 설정되면 루프 종료
		config.ServerAddress = deviceInfo.RecIP + ":20000" // 포트 번호는 적절히 변경하세요

		break
	}

	for {
		if err := runClient(); err != nil {
			fmt.Printf("Error: %v\n", err)
			time.Sleep(time.Duration(config.ReconnectInterval) * time.Second)
		}
	}
}

func getDeviceInfo(callNo string) (*DeviceInfo, error) {

	fmt.Print("Getting device info...\n")

	fmt.Printf("url:%s\n", config.RestAPIGetDevice)

	url := fmt.Sprintf("%s?c=%s", config.RestAPIGetDevice, callNo)

	fmt.Println(url)

	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var deviceInfo DeviceInfo
	err = json.Unmarshal(body, &deviceInfo)
	if err != nil {
		return nil, err
	}

	return &deviceInfo, nil
}

func setNetworkConfig(info *DeviceInfo) error {
	// 이 함수는 실제 시스템 설정을 변경합니다.
	// 실제 구현은 운영체제와 환경에 따라 다를 수 있습니다.
	fmt.Printf("Setting IP: %s\n", info.DLCIP)

	return setIPAddress("eth0", info.DLCIP, "24")

	// fmt.Printf("Setting Netmask: %s\n", info.DLCNetmask)
	// fmt.Printf("Setting Gateway: %s\n", info.DLCGateway)
	// 여기에 실제 네트워크 설정 코드를 추가하세요
	// return nil
}

func rebootSystem() {
	fmt.Println("Rebooting system...")
	// 실제 시스템 재부팅 코드를 여기에 추가하세요
	err := syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)

	if err != nil {
		fmt.Errorf("failed to reboot: %v", err)
	}
	os.Exit(0)
}

func setIPAddress(interfaceName, ipAddress, netmask string) error {

	fmt.Printf("Setting IP address: %s\n", ipAddress)

	cmd := exec.Command("ip", "addr", "add", ipAddress+"/"+netmask, "dev", interfaceName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		result := string(output)

		if strings.Contains(result, "File exists") {
			return nil

		}
		return fmt.Errorf("failed to set IP address: %v, output: %s", err, output)
	}
	return setIfconfigUp()
}

func setIfconfigUp() error {
	cmd := exec.Command("ip", "link", "set", "eth0", "up")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed to set interface up: eth0, err: %v, output: %s", err, output)
	}
	return nil
}

func runClient() error {
	var conn net.Conn
	var err error

	for i := 0; i < config.MaxRetries; i++ {
		conn, err = net.Dial("tcp", config.ServerAddress)
		if err == nil {
			break
		}
		fmt.Printf("Connection attempt %d failed: %v\n", i+1, err)
		time.Sleep(time.Duration(config.ReconnectInterval) * time.Second)
	}

	if err != nil {
		return fmt.Errorf("failed to connect after %d attempts", config.MaxRetries)
	}
	defer conn.Close()

	fmt.Println("Connected to server")

	for {
		if err := handleConnection(conn); err != nil {
			resetReciverConnectGPIO()
			return err
		}
	}
}

func handleConnection(conn net.Conn) error {

	setReciverConnectGPIO()

	for {
		conn.SetReadDeadline(time.Now().Add(time.Duration(config.ReadTimeout) * time.Second))

		sizeBuf := make([]byte, 1)
		_, err := conn.Read(sizeBuf)
		if err != nil {
			return fmt.Errorf("error reading size: %v", err)
		}

		size := int(sizeBuf[0])

		if size == 0 {
			continue
		}

		fmt.Printf("Received size: %d\n", size)
		buf := make([]byte, size-1)
		_, err = io.ReadFull(conn, buf)
		if err != nil {
			return fmt.Errorf("error reading data: %v", err)
		}

		fmt.Printf("Received data: %v\n", buf)
		fmt.Printf("Received size: %v\n", len(buf))
		fullData := append(sizeBuf, buf...)

		logEntry, err := Deserialize(fullData)
		if err != nil {
			return fmt.Errorf("deserialization error: %v", err)
		}

		formData, err := convertToFormData(logEntry)
		if err != nil {
			return fmt.Errorf("JSON conversion error: %v", err)
		}

		if err := sendToRESTAPIForm(formData); err != nil {
			return fmt.Errorf("REST API error: %v", err)
		}
	}
}

func Deserialize(data []byte) (*LogEntry, error) {
	fmt.Printf("Data: %v\n", data)

	le := &LogEntry{}
	le.Size = data[0]
	if le.Size == 0 {
		return nil, fmt.Errorf("invalid data length: expected , got %d", le.Size)
	}

	le.Type = data[1]
	le.DateTime = fmt.Sprintf("%02d-%02d-%02d %02d:%02d:%02d", (int(data[2]) + 2000), data[3], data[4], data[5], data[6], data[7])
	le.On = data[8]
	copy(le.Address[:], data[9:16])
	copy(le.Area[:], data[16:40])
	copy(le.Device[:], data[40:])

	return le, nil
}

func decodeEUCKR(b []byte) (string, error) {
	decoder := korean.EUCKR.NewDecoder()
	result, _, err := transform.Bytes(decoder, b)
	if err != nil {
		return "", err
	}
	return string(result), nil
}

func convertToFormData(le *LogEntry) (url.Values, error) {

	area, err := decodeEUCKR(bytes.TrimRight(le.Area[:], "\x00"))
	if err != nil {
		return nil, err
	}

	device, err := decodeEUCKR(bytes.TrimRight(le.Device[:], "\x00"))
	if err != nil {
		return nil, err
	}

	// Mapping le.Type to corresponding values
	var status string
	switch le.Type {
	case 1:
		le.Type = 6 // 화재
	case 2:
		le.Type = 2 // 고장

	case 3:
		le.Type = 3 // 설비
	case 4:
		le.Type = 4 // 출력

	}

	status = "해제"

	if le.On == 1 {
		status = "동작"
	}

	data := url.Values{}
	data.Set("callNo", config.CallNo)
	data.Set("type", fmt.Sprintf("%d", le.Type))
	data.Set("dateTime", le.DateTime)
	data.Set("status", status)
	data.Set("addr", fmt.Sprintf("%d%d-%d-%d%d%d-%d",
		le.Address[0], le.Address[1], le.Address[2],
		le.Address[3], le.Address[4], le.Address[5], le.Address[6]))
	data.Set("zoneNm", area)
	data.Set("deviceNm", device)

	return data, nil
}

func sendToRESTAPIForm(data url.Values) error {

	fmt.Printf("REST API Endpoint: %s\n", config.RestAPIEndpoint)
	fmt.Printf("Sending data to REST API: %v\n", data)

	resp, err := http.PostForm(config.RestAPIEndpoint, data)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("Error reading body: %v\n", err)
		return err
	}

	// 결과 출력
	fmt.Printf("Response Headers: %v\n", resp.Header)
	fmt.Printf("Response Status: %s\n", resp.Status)
	fmt.Printf("Response Body: %s\n", string(body))

	return nil
}

func setServerGPIO() error {
	cmd := exec.Command("sh", "-c", "echo 1 > /sys/class/gpio/gpio269/value")
	err := cmd.Run()
	if err != nil {
		fmt.Printf("GPIO 266 설정 실패: %v", err)
	}
	return nil
}

func resetServerGPIO() {
	cmd := exec.Command("sh", "-c", "echo 0 > /sys/class/gpio/gpio269/value")
	err := cmd.Run()
	if err != nil {
		fmt.Printf("GPIO 266 리셋 실패: %v", err)
	}
}

func setReciverConnectGPIO() error {
	cmd := exec.Command("sh", "-c", "echo 1 > /sys/class/gpio/gpio270/value")
	err := cmd.Run()
	if err != nil {
		fmt.Printf("GPIO 266 설정 실패: %v", err)
	}
	return nil
}

func resetReciverConnectGPIO() {
	cmd := exec.Command("sh", "-c", "echo 0 > /sys/class/gpio/gpio270/value")
	err := cmd.Run()
	if err != nil {
		fmt.Printf("GPIO 266 리셋 실패: %v", err)
	}
}

func updatePhoneNumberHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
		return
	}

	newPhoneNumber := r.FormValue("phone_number")
	if newPhoneNumber == "" {
		http.Error(w, "Phone number is required", http.StatusBadRequest)
		return
	}

	// 전화번호 형식 검증 (간단한 예시)
	if !strings.HasPrefix(newPhoneNumber, "010-") || len(newPhoneNumber) != 13 {
		http.Error(w, "Invalid phone number format", http.StatusBadRequest)
		return
	}

	err := updateConfigFile(newPhoneNumber)
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to update config: %v", err), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "Phone number updated successfully to %s", newPhoneNumber)
}

func updateConfigFile(newPhoneNumber string) error {
	config.CallNo = newPhoneNumber

	data, err := yaml.Marshal(&config)
	if err != nil {
		return fmt.Errorf("failed to marshal config: %v", err)
	}

	err = ioutil.WriteFile("config.yaml", data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write config file: %v", err)
	}

	return nil
}
