package main

import (
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"sync"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"src.whiteboxsystems.nl/DECOZO/okapidemo/sharedmodel"
	"src.whiteboxsystems.nl/DECOZO/okapidemo/whiteboxservice/model"
)

type UIService struct {
	srv        *http.Server
	inited     bool
	clientCert tls.Certificate
	data       *gorm.DB
}

func (srv *UIService) LoadData(location string) error {
	var err error
	srv.data, err = model.GetDB(location)
	return err
}

func (srv *UIService) Addr() string {
	if srv.srv == nil {
		return ""
	}
	return srv.srv.Addr
}

func (srv *UIService) ListenAndServe() {
	if !srv.inited {
		srv.init()
	}
	log.Println("Listening on %v", srv.srv.Addr)
	srv.srv.ListenAndServe()
}

func (srv *UIService) Shutdown(ctx context.Context) error {
	return srv.srv.Shutdown(ctx)
}

func (srv *UIService) init() {
	r := srv.srv.Handler.(*gin.Engine)

	r.LoadHTMLGlob("templates/*")
	r.Static("/assets", "./assets")

	r.Use(srv.Authenticate)
	r.GET("/", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "/ui")
	})
	r.GET("/ui", srv.GetIndex)
	r.GET("/ui/*page", srv.GetIndex)
	r.GET("/api/connections", srv.GetConnections)
	r.GET("/api/connections/:connID", srv.GetConnection)
	r.GET("/api/connections/:connID/:serviceID", srv.GetSubscriptions)
	r.GET("/api/connections/:connID/:serviceID/:patientID", srv.GetPatient)
	r.GET("/api/registrations", srv.GetRegistrations)
	srv.inited = true
}

func (srv *UIService) GetIndex(c *gin.Context) {
	c.HTML(http.StatusOK, "index.html", gin.H{"externalURL": extRpcAddr})
}

func (srv *UIService) GetConnection(c *gin.Context) {
	connID := c.Param("connID")
	connection := &sharedmodel.Connection{}
	srv.data.Where("id = ?", connID).Find(&connection)
	c.JSON(http.StatusOK, connection)
}
func (srv *UIService) GetSubscriptions(c *gin.Context) {
	connID := c.Param("connID")
	serviceID := c.Param("serviceID")
	serviceConfig := &sharedmodel.ServiceConfig{}
	srv.data.Preload("Service").Preload("Subscriptions").Where("connection_id = ? and id = ?", connID, serviceID).Find(&serviceConfig)
	c.JSON(http.StatusOK, serviceConfig)
}

func (srv *UIService) GetPatient(c *gin.Context) {
	connID := c.Param("connID")
	serviceID := c.Param("serviceID")
	patientID := c.Param("patientID")
	patient := &sharedmodel.Subscription{}
	serviceConfig := &sharedmodel.ServiceConfig{}
	if err := srv.data.Preload("FetchProtocol").Preload("FetchProtocol.AuthConfig").Preload("Service").Where("connection_id = ? and id = ?", connID, serviceID).Find(&serviceConfig).Error; err != nil {
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}
	srv.data.Where("service_config_id = ? and id = ?", serviceID, patientID).Find(&patient)

	protoconfig := map[string]string{}
	protometa := map[string]string{}

	err := serviceConfig.FetchProtocol.UnmarshalConfig(&protoconfig)

	if err != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	err = patient.GetProtocolMeta(&protometa)

	if err != nil {
		c.AbortWithStatus(http.StatusBadRequest)
		return
	}

	url := fmt.Sprintf("%v/%v/%v", protoconfig["url"], "patients", protometa["patientID"])

	req, _ := http.NewRequest("GET", url, nil)

	client := &http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
				Certificates:       []tls.Certificate{srv.clientCert},
			},
		},
	}

	resp, err := client.Do(req)

	if err != nil {
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	if resp.StatusCode >= 400 {
		c.AbortWithError(resp.StatusCode, fmt.Errorf("%v", resp.Status))
		return
	}

	cmd := exec.Command(binFolder+"/ediviewer", binFolder+"/medeur.json", binFolder+"/template.html", "/dev/stdin")
	sin, err := cmd.StdinPipe()
	if err != nil {
		log.Println("[ediviewer] Failed to open stdin pipe:", err)
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}
	defer sin.Close()

	serr, err := cmd.StderrPipe()
	if err != nil {
		log.Println("[ediviewer] Failed to open stderr pipe:", err)
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}
	defer serr.Close()

	sout, err := cmd.StdoutPipe()
	if err != nil {
		log.Println("[ediviewer] Failed to open stdout pipe:", err)
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}
	defer sout.Close()

	if err := cmd.Start(); err != nil {
		log.Println("[ediviewer] Failed to start:", err)
		c.AbortWithError(http.StatusInternalServerError, err)
		return
	}

	wg := sync.WaitGroup{}
	wg.Add(3)

	go func() {
		defer wg.Done()
		if _, err := io.Copy(sin, resp.Body); err != nil {
			log.Println("[ediviewer] Error reading EDIFACT:", err)
			c.AbortWithError(http.StatusInternalServerError, err)
		}
	}()

	go func() {
		defer wg.Done()
		if _, err := io.Copy(c.Writer, sout); err != nil {
			log.Println("[ediviewer] Error output EDIFACT:", err)
			c.AbortWithError(http.StatusInternalServerError, err)
		}
	}()

	go func() {
		defer wg.Done()
		if _, err := io.Copy(os.Stderr, serr); err != nil {
			log.Println("[ediviewer] Error stderr EDIFACT:", err)
			c.AbortWithError(http.StatusInternalServerError, err)
		}
	}()

	wg.Wait()

	if err := cmd.Wait(); err != nil {
		log.Println("[ediviewer] Failed:", err)
		c.AbortWithError(http.StatusInternalServerError, err)
	}
}

func (srv *UIService) Authenticate(c *gin.Context) {
	// Maybe authenticate user
}

func (srv *UIService) GetConnections(c *gin.Context) {
	connections := []*sharedmodel.Connection{}
	srv.data.Preload("Services").Preload("Services.Service").Preload("Services.Subscriptions").Find(&connections)
	c.JSON(http.StatusOK, connections)
}

func (srv *UIService) GetRegistrations(c *gin.Context) {
	registrations := []*sharedmodel.Registration{}
	srv.data.Where("status = ?", sharedmodel.RegistrationStatusPending).Find(&registrations)
	c.JSON(http.StatusOK, registrations)
}

func NewUIServer(addr string) *UIService {
	cert := loadCert()

	srv := &UIService{srv: &http.Server{
		Addr:    addr,
		Handler: gin.Default(),
	}, clientCert: *cert}

	return srv
}
