package models

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"net"
	"time"

	"git.paulbsd.com/paulbsd/ipbl/src/config"
)

const ScanLimit = 500

var lastday = time.Now().Add(-(time.Hour * 24))

func GetIPs(ctx *context.Context, config *config.Config, limit int) (apiips []*APIIP, err error) {
	var ips []IP
	err = config.Db.Limit(limit).Desc("created").Find(&ips)
	for _, ml := range ips {
		apiips = append(apiips, ml.APIFormat())
	}
	return
}

func GetIPsLast(ctx *context.Context, config *config.Config, interval string) (apiips []string, err error) {
	var ips []IP
	err = config.Db.Where("updated >= (now()-?::interval)", interval).GroupBy("ip").Find(&ips)
	for _, ml := range ips {
		apiips = append(apiips, ml.IP)
	}
	return
}

func GetIP(ctx *context.Context, cfg *config.Config, ipquery interface{}) (apiip *APIIP, err error) {
	session := cfg.Db.NewSession()
	defer session.Close()
	var ip IP
	has, err := session.Where("ip = ?", ipquery).Get(&ip)
	if !has {
		err = fmt.Errorf("not found")
		return nil, err
	}
	if err != nil {
		return
	}
	apiip = ip.APIFormat()
	session.Commit()
	return
}

func (ip *IP) UpdateRDNS() (result string, err error) {
	res, err := net.LookupAddr(ip.IP)
	if err != nil {
		result = ""
		return
	}
	result = res[0]
	return
}

func (ip *IP) InsertOrUpdate(cfg *config.Config) (numinsert int64, numupdate int64, err error) {
	session := cfg.Db.NewSession()
	defer session.Close()
	var ips = []IP{}
	err = session.Where("ip = ?", ip.IP).And("src = ?", ip.Src).And("hostname = ?", ip.Hostname).Find(&ips)
	if len(ips) > 0 {
		numupdate, err = session.Where("id = ?", ips[0].ID).Cols("updated").Update(&IP{})
		if err != nil {
			log.Println(err)
		}
	} else {
		numinsert, err = session.Insert(ip)
		if err != nil {
			log.Println(err)
		}
	}
	session.Commit()
	return
}

func InsertIPBulk(cfg *config.Config, ips *[]IP) (numinsert int64, numupdate int64, err error) {
	for _, ip := range *ips {
		numinsert, numupdate, err = ip.InsertOrUpdate(cfg)
	}
	Cleanup(cfg)
	return
	/*var iplist []string
	hostname := (*ips)[0].Hostname.String
	for _, ip := range *ips {
		iplist = append(iplist, ip.IP)
	}

	var searchips []IP
	cfg.Db.In("ip", iplist).Where("hostname = ?", hostname).Find(&searchips)

	var toupdateips []string
	for _, ip := range searchips {
		toupdateips = append(toupdateips, ip.IP)
	}
	numupdate, _ = cfg.Db.In("ip", toupdateips).Where("hostname = ?", hostname).Cols("updated").Update(&IP{})

	var toinsertip, _ = differ(*ips, searchips)
	numinsert, err = cfg.Db.Insert(toinsertip)

	Cleanup(cfg)

	return*/
}

func ScanIP(cfg *config.Config) (err error) {
	for {
		orphans := []IP{}
		session := cfg.Db.NewSession()
		err := session.Begin()
		if err != nil {
			log.Println(err)
		}
		if session.Where("rdns IS NULL").Asc("ip").Limit(ScanLimit).Find(&orphans); len(orphans) > 0 {
			for _, orphan := range orphans {
				reverse, _ := orphan.UpdateRDNS()
				log.Printf("%s -> \"%s\"\n", orphan.IP, reverse)
				orphan.Rdns.String = reverse
				orphan.Rdns.Valid = true
				_, err = session.ID(orphan.ID).Cols("rdns").Update(&orphan)

				if err != nil {
					session.Rollback()
					log.Println(err)
				}
			}
			err = session.Commit()
			if err != nil {
				log.Println(err)
			}
		} else {
			time.Sleep(10 * time.Second)
		}
		session.Close()
	}
}

func Cleanup(cfg *config.Config) (err error) {
	session := cfg.Db.NewSession()
	defer session.Close()
	results, _ := session.Query("select * from ip where ip in (select ip from ip group by ip having count(ip) > 1) and hostname is null order by updated desc;")
	if len(results) > 0 {
		_, err := session.Query("delete from ip where ip in (select ip from ip group by ip having count(ip) > 1) and hostname is null;")
		if err != nil {
			log.Println("error deleting orphans")
		}
	}
	session.Commit()
	return
}

func (ip *IP) APIFormat() *APIIP {
	if ip == nil {
		return nil
	}
	return &APIIP{
		IP:       ip.IP,
		Rdns:     ip.Rdns.String,
		Src:      ip.Src,
		Hostname: ip.Hostname.String,
		City:     ip.City.String,
		Country:  ip.Country.String,
		Date:     ip.Updated.Local().String(),
	}
}
func (ip *APIIP) APIConvert() *IP {
	if ip == nil {
		return nil
	}
	return &IP{
		IP: ip.IP,
		Rdns: sql.NullString{
			String: ip.Rdns,
			Valid:  false},
		Src: ip.Src,
		Hostname: sql.NullString{
			String: ip.Hostname,
			Valid:  true},
	}
}

type IP struct {
	ID       int            `xorm:"pk autoincr"`
	IP       string         `xorm:"text notnull unique(ipsrc)" json:"ip"`
	Rdns     sql.NullString `xorm:"text default"`
	Src      string         `xorm:"text notnull unique(ipsrc)" json:"src"`
	Hostname sql.NullString `xorm:"text default '' unique(ipsrc)" json:"hostname"`
	City     sql.NullString `xorm:"text default '' unique(ipsrc)" json:"city"`
	Country  sql.NullString `xorm:"text default '' unique(ipsrc)" json:"country"`
	Created  time.Time      `xorm:"created notnull"`
	Updated  time.Time      `xorm:"updated notnull"`
}

type APIIP struct {
	IP       string `json:"ip"`
	Rdns     string `json:"rdns"`
	Src      string `json:"src"`
	Hostname string `json:"hostname"`
	City     string `json:"city"`
	Country  string `json:"country"`
	Date     string `json:"date"`
}
