package main

import (
	"log"
	"net"
	"os"
	"path/filepath"
	"time"

	"github.com/google/gopacket"
	layers "github.com/google/gopacket/layers"
	"github.com/google/gopacket/pcapgo"
	"srs.epita.fr/fic-server/admin/sync"
	fic "srs.epita.fr/fic-server/libfic"
)

// This interface abstract pcap and pcapng data acquisition
// pcapgo.Reader and pcapgo.NgReader both use signature :
// ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error)
type PcapPacketDataReader interface {
	ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error)
}

// Wrap pcago.Reader as we can't impl its interface outside its package
type PcapReader struct {
	*pcapgo.Reader
}

// Wrap pcago.NgReader as we can't impl its interface outside its package
type PcapNgReader struct {
	*pcapgo.NgReader
}

// Impl interface for reading pcap and pcapng data
func (pcapReader *PcapReader) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
	return pcapReader.Reader.ReadPacketData()
}

func (pcapNGReader *PcapNgReader) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) {
	return pcapNGReader.NgReader.ReadPacketData()
}

// Iterate thought each packet to find potentialy unwanted packets
// TODO: Allow custom rules to specify what is a unwanted packet
func CheckPcap(pcapReader PcapPacketDataReader, pcapName string) (errs error) {
	warningFlows := make(map[gopacket.Flow]([]time.Time))

	//
	// Handle packets from network layer section
	//
	data, ci, err := pcapReader.ReadPacketData()
	for ; err == nil; data, ci, err = pcapReader.ReadPacketData() {

		packet := gopacket.NewPacket(data, layers.LayerTypeEthernet, gopacket.Default)
		packetNetworkLayer := packet.NetworkLayer()

		// No network layer
		if packetNetworkLayer == nil {
			continue
		}

		flow := packetNetworkLayer.NetworkFlow()

	ENDPOINT_SELECTION:
		switch flow.EndpointType() {

		case layers.EndpointIPv4, layers.EndpointIPv6:
			src, dst := flow.Endpoints()

			if net.ParseIP(src.String()).IsPrivate() &&
				net.ParseIP(dst.String()).IsPrivate() {

				warningFlows[flow] = append(warningFlows[flow], ci.Timestamp)
			}

		// Really ?
		default:
			break ENDPOINT_SELECTION
		}
	}

	if os.Getenv("VERBOSE_PCAP_CHECK") != "" {
		for flow, timestamps := range warningFlows {
			log.Printf(
				"(%s) Found communication between two private IPs (%s => %s) at timestamps:",
				pcapName,
				flow.Src().String(),
				flow.Dst().String())

			for _, timestamp := range timestamps {
				log.Printf("\t%s", timestamp)
			}
		}
	}

	if len(warningFlows) > 0 {
		log.Printf("/!\\ %s: Found %d endpoints communicating with private IPs \n => Set VERBOSE_PCAP_CHECK env variable for details",
			pcapName,
			len(warningFlows))
	} else {
		log.Printf("%s: No endpoints communicating with private IPs \n",
			pcapName)
	}

	return
}

func CheckTextFile(fd *os.File) (errs error) {
	return
}

func InspectFileForIPAddr(file *fic.EFile, _ *fic.Exercice, exceptions *sync.CheckExceptions) (errs error) {
	fd, closer, err := sync.GetFile(sync.GlobalImporter, file.GetOrigin())
	if err != nil {
		log.Printf("Unable to open %q: %s", file.GetOrigin(), err.Error())
		return
	}
	defer closer()

	switch filepath.Ext(file.Name) {
	case ".pcap":
		pcapReader, err := pcapgo.NewReader(fd)
		if err != nil {
			log.Printf("Unable to load the pcap file, please check if it is a real pcap file")
			return
		}

		errs = CheckPcap(&PcapReader{pcapReader}, file.Name)

	case ".pcapng":
		pcapNgReader, err := pcapgo.NewNgReader(fd, pcapgo.DefaultNgReaderOptions)
		if err != nil {
			log.Printf("Unable to load the pcapng file, please check if it is a real pcapng file")
			return
		}
		errs = CheckPcap(&PcapNgReader{pcapNgReader}, file.Name)

	default:
		return

	}

	return
}
