package upnp

import (
	"fmt"
	"strconv"
	"strings"

	omd "git.loafle.net/overflow/model/discovery"
	omm "git.loafle.net/overflow/model/meta"
	omu "git.loafle.net/overflow/model/util"
	oun "git.loafle.net/overflow/util-go/net"
	"git.loafle.net/overflow_scanner/probe/discovery/session"
	"github.com/huin/goupnp"
)

const (
	TargetRootDevice = "upnp:rootdevice"
	TargetSSDPAll    = "ssdp:all"
)

func Scan(discoverySession session.DiscoverySession) error {
	devs, err := goupnp.DiscoverDevices(TargetRootDevice)
	if nil != err {
		fmt.Println("DiscoverDevices: ", err)
		return nil
	}
	metaIPTypeEnum := omm.ToMetaIPTypeEnum(discoverySession.Zone().MetaIPType)

LOOP:
	for _, dev := range devs {
		rd := dev.Root.Device
		if !rd.PresentationURL.Ok {
			continue LOOP
		}

		pURL := rd.PresentationURL.URL

		ip := oun.ParseIP(pURL.Hostname())
		if nil == ip {
			continue LOOP
		}

		switch metaIPTypeEnum {
		case omm.MetaIPTypeEnumV4:
			if 4 != ip.Version() {
				continue LOOP
			}
		case omm.MetaIPTypeEnumV6:
			if 6 != ip.Version() {
				continue LOOP
			}
		default:
			continue LOOP
		}

		meta := map[string]string{
			"DeviceType":       rd.DeviceType,
			"FriendlyName":     rd.FriendlyName,
			"Manufacturer":     rd.Manufacturer,
			"ManufacturerURL":  rd.ManufacturerURL.Str,
			"ModelName":        rd.ModelName,
			"ModelDescription": rd.ModelDescription,
			"ModelNumber":      rd.ModelNumber,
			"SerialNumber":     rd.SerialNumber,
			"UDN":              rd.UDN,
			"UPC":              rd.UPC,
		}

		h := omd.NewHost(
			discoverySession.Zone(),
			discoverySession.Zone().MetaIPType,
			ip.String(),
		)
		h.Name = rd.ModelName
		h.DiscoveredDate = omu.NowPtr()

		discoverySession.AddHost(
			omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumUPnP),
			h,
			meta,
		)

		portNumber, err := strconv.Atoi(pURL.Port())
		if nil != err {
			continue LOOP
		}

		p := omd.NewPort(
			h,
			omm.ToMetaPortType(omm.MetaPortTypeEnumTCP),
			portNumber,
		)
		p.DiscoveredDate = omu.NowPtr()

		p = discoverySession.AddPort(
			omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumUPnP),
			p,
			meta,
		)

		metaCryptoType := omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumNONE)
		serviceType := omm.MetaServiceTypeEnumUNKNOWN.String()
		serviceKey := "UNKNOWN"

		switch strings.ToLower(pURL.Scheme) {
		case "http":
			serviceKey = "HTTP"
			serviceType = omm.MetaServiceTypeEnumWeb.String()
		case "https":
			serviceKey = "HTTPS"
			metaCryptoType = omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumTLS)
			serviceType = omm.MetaServiceTypeEnumWeb.String()
		default:
		}

		s := omd.NewService(
			p,
			metaCryptoType,
			serviceKey,
		)
		s.Name = pURL.Scheme
		s.ServiceType = serviceType
		s.DiscoveredDate = omu.NowPtr()

		discoverySession.AddService(
			omm.ToMetaDiscovererType(omm.MetaDiscovererTypeEnumUPnP),
			s,
			meta,
		)

		select {
		case <-discoverySession.StopChan():
			return nil
		default:
		}
	}

	return nil
}