package internet

import (
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"

	"gitea.unprism.ru/KRBL/sim-modem/api/modem/at"
)

var apns = map[string]string{
	"Tinkoff": "m.tinkoff",
}

const pppConfigName = "annalistnet"
const pppConfig = `
# Annalist project custom internet connection

# APN:
connect "/usr/sbin/chat -v -f /etc/chatscripts/gprs -T %s"

# Port:
%s

# Baudrate:
%d

noipdefault
usepeerdns
defaultroute
persist
noauth
nocrtscts
local
`

type conn struct {
	logger *log.Logger
	port   at.Port
}

type Conn interface {
	Init() error
	ConfigurePPP() error
	Ping() bool // Is connected
	io.Closer
}

func New(logger *log.Logger, port at.Port) Conn {
	return &conn{
		logger: logger,
		port:   port,
	}
}

func (c *conn) checkPackageExist(pname string) bool {
	resp, err := exec.Command("apt-mark", "showmanual", pname).Output()
	c.logger.Println("CHECK:", resp)
	if err != nil {
		c.logger.Println("CHECK PACKAGE ERROR: ", err.Error())
		return false
	}
	return string(resp[:len(pname)]) == pname
}

func (c *conn) ensurePackage(pname string) error {
	if c.checkPackageExist(pname) {
		return nil
	}
	return fmt.Errorf("package %s not installed", pname)
	// c.logger.Println("Installing", pname, "package...")
	// resp, err := exec.Command("apt-get", "install", pname).Output()
	// if err != nil {
	// return fmt.Errorf("execute install cmd: %w", err)
	// }
	// c.logger.Println(resp)
	// c.logger.Println("\x1b[38;2;255;0;0mComplete\x1b[38;2;255;255;255m")
	// return nil
}

// Check requirenments
func (c *conn) checkReqs() error {
	// Check AT port for sure
	if c.port == nil || !c.port.IsConnected() {
		return fmt.Errorf("AT port is not connect or nil")
	}

	// Ensure all necessary packages installed
	if err := c.ensurePackage("ppp"); err != nil {
		return fmt.Errorf("ensure ppp package: %w", err)
	}
	// if err := c.ensurePackage("net-tools"); err != nil {
	// 	return fmt.Errorf("ensure net-tools package: %w", err)
	// }

	// Check SIM is valid
	//   AT+CPIN? and just check
	resp, err := c.port.Send("AT+CPIN?")
	if err != nil {
		return fmt.Errorf("AT+CPIN? request: %w", err)
	}
	if !resp.Check() {
		return fmt.Errorf("validate SIM: error response: %s", resp)
	}
	return nil
}

func (c *conn) ConfigurePPP() error {
	// Get provider name and its APN
	resp, err := c.port.Send("AT+CSPN?")
	if err != nil {
		return fmt.Errorf("AT+CSPN? request: %w", err)
	}
	if !resp.Check() {
		return fmt.Errorf("get provider: error response: %s", resp)
	}
	strs := strings.Split(string(resp), "\"")
	if len(strs) < 3 {
		return fmt.Errorf("parse AT+CSPN response: %s", string(resp))
	}
	provider := strs[1]
	apn := apns[provider]
	if apn == "" {
		return fmt.Errorf("no apn for provider: %s", provider)
	}

	// Make config
	c.logger.Printf("Config values: %s, %s, %d", apn, c.port.GetName(), c.port.GetBaudrate())
	config := fmt.Sprintf(pppConfig, apn, c.port.GetName(), c.port.GetBaudrate())

	// Write to file
	f, err := os.OpenFile("/etc/ppp/peers/"+pppConfigName, os.O_CREATE|os.O_WRONLY, 0666)
	if err != nil {
		return fmt.Errorf("open ppp config file %w", err)
	}
	defer f.Close()
	if _, err := f.Write([]byte(config)); err != nil {
		return fmt.Errorf("write to ppp config file: %w", err)
	}
	return nil
}

func (c *conn) setup() error {
	c.logger.Println("Check requirenments...")
	if err := c.checkReqs(); err != nil {
		return fmt.Errorf("check requirenments: %w", err)
	}
	// DEBUG show networks and signal
	resp, err := c.port.Send("AT+COPS?")
	if err != nil {
		return fmt.Errorf("AT+COPS? request: %w", err)
	}
	c.logger.Println("DEBUG networks:", resp)

	resp, err = c.port.Send("AT+CSQ")
	if err != nil {
		return fmt.Errorf("AT+CSQ request: %w", err)
	}
	c.logger.Println("DEBUG signal quality:", resp)

	// Configure ppp
	// what is better ASK If /etc/ppp/peers/annalistnet not exists
	c.logger.Println("Configure ppp...")
	if err := c.ConfigurePPP(); err != nil {
		return fmt.Errorf("configure ppp: %w", err)
	}
	return nil
}

func (c *conn) connect() error {
	resp, err := exec.Command("pon", pppConfigName).Output()
	if err != nil {
		return fmt.Errorf("execute connect cmd: %w", err)
	}
	c.logger.Println("DEBUG pon response:", string(resp))
	return nil
}

func (c *conn) diconnect() error {
	resp, err := exec.Command("poff", pppConfigName).Output()
	if err != nil {
		return fmt.Errorf("execute disconnect cmd: %w", err)
	}
	c.logger.Println("DEBUG poff response:", string(resp))
	return nil
}

func (c *conn) Init() error {
	// Setup
	c.logger.Println("Setup...")
	if err := c.setup(); err != nil {
		return fmt.Errorf("setup: %w", err)
	}
	// Connect
	c.logger.Println("Connect...")
	if err := c.connect(); err != nil {
		return fmt.Errorf("connect: %w", err)
	}

	//DEBUG
	resp, err := exec.Command("ifconfig").Output()
	if err != nil {
		return fmt.Errorf("execute ifconfig cmd: %w", err)
	}
	c.logger.Println("DEBUG ifconfig resp:", string(resp))

	// Test connectin using Ping
	c.logger.Println("Test...")
	if !c.Ping() {
		return fmt.Errorf("ping failed")
	}
	return nil
}

func (c *conn) Ping() bool {
	// Test - try to connect to Google DNS
	//   ping -I ppp0 8.8.8.8
	resp, err := exec.Command("ping", "8.8.8.8").Output()
	if err != nil {
		c.logger.Println("Ping 1 cmd error:", err)
	}
	c.logger.Println("Ping 1 resp:", string(resp))

	resp, err = exec.Command("ping", "-I", "ppp0", "8.8.8.8").Output()
	if err != nil {
		c.logger.Println("Ping 2 cmd error:", err)
	}
	c.logger.Println("Ping 2 resp:", string(resp))

	return !(strings.Contains(string(resp), "Destination Host Unreachable") || strings.Contains(string(resp), "Destination Net Unreachable")) // tmp solution
}

func (c *conn) Close() error {
	if err := c.diconnect(); err != nil {
		return fmt.Errorf("diconnect: %w", err)
	}
	return nil
}