package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"slices"
	"strconv"
	"strings"
	"sync"
	"time"
)

type MiogestHandler struct {
	Caratterisiche        ListaCaratteristiche
	Categorie             []Categoria
	AnnunciXML            AnnunciXML
	AnnunciParsed         AnnunciParsed
	annunci_cache_expires time.Time
	vars_cache_expires    time.Time
}

func NewMiogestHandler() *MiogestHandler {
	return &MiogestHandler{
		Caratterisiche:        ListaCaratteristiche{},
		Categorie:             nil,
		AnnunciXML:            AnnunciXML{},
		AnnunciParsed:         AnnunciParsed{},
		annunci_cache_expires: time.Now(),
		vars_cache_expires:    time.Now(),
	}
}

// ANNUNCI FROM MIOGEST
func (m *MiogestHandler) GetAnnunci() AnnunciXML {
	if len(m.AnnunciXML.Annuncio) == 0 || time.Now().After(m.annunci_cache_expires) {
		m.GetAnnunciFromMiogest()
	}
	return m.AnnunciXML
}

func (m *MiogestHandler) GetAnnunciFromMiogest() {
	m.AnnunciXML = GetDataXML[AnnunciXML]("http://partner.miogest.com/agenzie/infoalloggi.xml")
	m.annunci_cache_expires = time.Now().Add(1 * time.Hour)
}

// CATEGORIE AND CARATTERISTICHE FROM MIOGEST
func (m *MiogestHandler) InitDefaults() {
	m.GenerateCaratteristiche()
	m.GenerateCategorie()
}

func (m *MiogestHandler) GetCaratteristiche() ListaCaratteristiche {
	if len(m.Caratterisiche.Caratteristiche) == 0 || time.Now().After(m.vars_cache_expires) {
		m.GenerateCaratteristiche()
	}
	return m.Caratterisiche
}

func (m *MiogestHandler) GetCategorie() []Categoria {
	if len(m.Categorie) == 0 || time.Now().After(m.vars_cache_expires) {
		m.GenerateCategorie()
	}
	return m.Categorie
}

func (m *MiogestHandler) GenerateCaratteristiche() {
	var result = GetDataXML[DefaultCaratteristiche]("https://www.miogest.com/apps/revo.aspx?tipo=schede")
	var array ListaCaratteristiche
	for _, tag := range result.Scheda {
		var c = Caratteristica{Id: tag.Id, Tagxml: tag.Tagxml}
		array.Caratteristiche = append(array.Caratteristiche, c)
	}
	m.Caratterisiche = array
	m.vars_cache_expires = time.Now().Add(1 * time.Hour)
}

func (m *MiogestHandler) GenerateCategorie() {
	var result = GetDataXML[DefaultCategories]("https://www.miogest.com/apps/revo.aspx?tipo=categorie")
	var listaCategorie []Categoria
	for _, elem := range result.Cat {
		var c = Categoria{Id: elem.ID, Nome: elem.Nome}
		listaCategorie = append(listaCategorie, c)
	}
	m.Categorie = listaCategorie
	m.vars_cache_expires = time.Now().Add(1 * time.Hour)
}

// ANNUNCI PARSED
func (m *MiogestHandler) GetAnnunciParsed() AnnunciParsed {
	m.ParseAnnunci()
	return m.AnnunciParsed
}

func (m *MiogestHandler) ParseAnnunci() {
	annunci := m.GetAnnunci()
	cod_image_to_update := []string{}
	if imageOption {
		var err error
		cod_image_to_update, err = ImageManager.FindIsUpdate(&annunci)
		if err != nil {
			log.Fatalf("Failed to find images to update: %v", err.Error())
		}
		if err := ImageManager.ClearImages(cod_image_to_update); err != nil {
			log.Fatalf("Failed to clear images: %v", err.Error())
		}
		ImageManager.InsertImagesRef(&annunci)
	}

	var AnnunciArray AnnunciParsed
	AnnunciArray.Annuncio = make([]AnnuncioParsed, 0, len(annunci.Annuncio))
	Miogest.InitDefaults()
	var wg sync.WaitGroup
	wg.Add(len(annunci.Annuncio))
	sem := make(chan struct{}, concurrentImages) //Limit concurrent goroutines
	lista_codici := []string{}
	for i := range annunci.Annuncio {

		lista_codici = append(lista_codici, RemoveWhitespace(*annunci.Annuncio[i].Codice))
		sem <- struct{}{}
		go func(i int) {
			defer wg.Done()
			defer func() { <-sem }()
			result := extractData_update(&annunci.Annuncio[i], contains(cod_image_to_update, *annunci.Annuncio[i].Codice))
			AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result)
		}(i)
	}
	wg.Wait()
	if imageOption {
		for _, codice := range lista_codici {
			err := folderCleanup(codice)
			if err != nil {
				log.Fatalf("Failed to cleanup folder: %v", err.Error())
			}
		}
	}
	m.AnnunciParsed = AnnunciArray
}

var re = regexp.MustCompile(`[^+\d]`)

func boolUpd(b bool) *bool {
	return &b
}

func strUpd(s string) *string {
	return &s
}

func extractData_update(annuncio *AnnuncioXML, updateImages bool) AnnuncioParsed {
	var tmp AnnuncioParsed = AnnuncioParsed{}
	tmp.Codice = RemoveWhitespace(GetStringValue(annuncio.Codice))
	processLocatore(&tmp, annuncio)
	processIndirizzo(&tmp, annuncio)
	processTipologia(&tmp, annuncio)
	tmp.Consegna = parseConsegna(annuncio.Consegna)
	processCategorie(&tmp, annuncio)
	processDatiImmobile(&tmp, annuncio)
	tmp.Stato = annuncio.Stato

	tmp.Homepage = boolUpd(false)
	if annuncio.HomePage != nil {
		if *annuncio.HomePage == "si" {

			tmp.Homepage = boolUpd(true)
		}
	}
	layout := "20060102150405"
	if annuncio.Creato != nil {
		t, err := time.Parse(layout, *annuncio.Creato)
		if err != nil {
			tmp.Creato_il = nil
		} else {
			tmp.Creato_il = &t
		}
	}
	if annuncio.Modifica != nil {
		t, err := time.Parse(layout, *annuncio.Modifica)
		if err != nil {
			tmp.Modificato_il = nil
		} else {
			tmp.Modificato_il = &t
		}
	}
	tmp.Titolo_it = annuncio.Titolo
	tmp.Titolo_en = annuncio.TitoloEn
	tmp.Desc_it = annuncio.Descrizione
	tmp.Desc_en = annuncio.DescrizioneEn

	tmp.Web = boolUpd(true)

	videos := []string{}
	if annuncio.Video != nil {
		videos = *annuncio.Video
	}
	miogestVideos := []string{}
	if annuncio.AMMedias != nil {
		for _, media := range *annuncio.AMMedias {
			if media.AMVideo != nil && media.AMVideo.Url != nil {
				miogestVideos = append(miogestVideos, *media.AMVideo.Url)
			}
		}
	}
	concatenatedVideos := slices.Concat(videos, miogestVideos)
	tmp.Url_video = &concatenatedVideos

	if annuncio.Accessori != nil {
		tmp.Accessori = annuncio.Accessori
	}
	tmpcaratt := parseCaratteristiche(annuncio)
	tmp.Caratteristiche = &tmpcaratt
	if updateImages {
		processImages(&tmp, annuncio)
	}
	folders := GetPathDirsArray(filepath.Join(imgbasepath, tmp.Codice))
	for _, folder := range folders {
		folderpath := filepath.Join(filepath.Join(imgbasepath, tmp.Codice), folder)
		files, err := os.ReadDir(folderpath)
		if err != nil {
			panic(err)
		}
		for _, file := range files {
			if strings.Contains(file.Name(), "webp") && !strings.HasPrefix(file.Name(), "thumbnail") {
				tmpImgUrl := tmp.Codice + "/" + folder
				if tmp.Url_foto == nil {
					tmp.Url_foto = &[]string{}
				}
				*tmp.Url_foto = append(*tmp.Url_foto, tmpImgUrl)
			}
		}
	}
	return tmp
}

func processLocatore(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
	if annuncio.Clienti == nil || annuncio.Clienti.Cliente == nil {
		return
	}
	tmp.Email = annuncio.Clienti.Cliente.Email1

	var number string
	for _, tel := range []*string{annuncio.Clienti.Cliente.Tel1, annuncio.Clienti.Cliente.Tel2, annuncio.Clienti.Cliente.Tel3} {
		if tel != nil {
			cleanedTel := re.ReplaceAllString(*tel, "")
			if cleanedTel != "" {
				number = cleanedTel
				break
			}
		}
	}
	tmp.Numero = &number
	if annuncio.Clienti.Cliente.Cognome != nil && annuncio.Clienti.Cliente.Nome != nil {
		tmp.Locatore = strUpd(fmt.Sprintf("%s %s", MakeUppercase(*annuncio.Clienti.Cliente.Cognome), MakeUppercase(*annuncio.Clienti.Cliente.Nome)))
	}
	tmp.Idlocatore = annuncio.Clienti.Cliente.Id
}

func processIndirizzo(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
	tmp.Indirizzo = strUpd(MakeUppercase(GetStringValue(annuncio.Indirizzo)))
	tmp.Civico = annuncio.Civico
	tmp.Comune = strUpd(MakeUppercase(GetStringValue(annuncio.Comune)))
	tmp.Cap = annuncio.Cap
	tmp.Provincia = annuncio.Provincia
	tmp.Regione = annuncio.Regione
	tmp.Lat = annuncio.Latitudine
	tmp.Lon = annuncio.Longitudine
	tmp.Indirizzo_secondario = strUpd(MakeUppercase(GetStringValue(annuncio.IndirizzoSecondario)))
	tmp.Civico_secondario = annuncio.CivicoSecondario
	tmp.Lat_secondario = annuncio.LatitudineSecondario
	tmp.Lon_secondario = annuncio.LongitudineSecondario
}

func processTipologia(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
	if annuncio.Tipologia == nil {
		tmp.Tipo = strUpd("Errore")
		return
	}
	if *annuncio.Tipologia == "V" {
		tmp.Tipo = strUpd("Vendita")
		return
	}
	if *annuncio.Tipologia == "I" {
		tmp.Tipo = strUpd("Cessione")
		return
	}
	if *annuncio.Tipologia == "A" {
		if *annuncio.Tipologia2 == "Residenziale" || *annuncio.Tipologia2 == "Turistico" || *annuncio.Tipologia2 == "Stanza" {
			tmp.Tipo = strUpd("Transitorio")
		} else {
			tmp.Tipo = strUpd("Stabile")
		}
		return
	}
	tmp.Tipo = strUpd("Errore")

}

func processCategorie(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
	if annuncio.Categoria == nil {
		return
	}
	resultArray := make([]string, 0)
	cats := *annuncio.Categoria
	defaultCategorie := Miogest.GetCategorie()
	for _, cat := range cats {
		for _, catdef := range defaultCategorie {
			if cat == catdef.Id {
				resultArray = append(resultArray, catdef.Nome)
			}
		}
	}
	if tmp.Categorie == nil {
		tmp.Categorie = &[]string{}
	}
	*tmp.Categorie = resultArray
}

func prezzoParse(prezzo *string) int {
	zero := 0
	if prezzo == nil {
		return zero
	}
	f, err := strconv.ParseFloat(strings.TrimSpace(strings.ReplaceAll(*prezzo, ",", ".")), 64)
	if err != nil {
		log.Printf("Failed to parse price: %v", err)
		return zero
	}
	prezzoInt := int(f * 100) // Convert to cents
	return prezzoInt
}

func processDatiImmobile(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
	tmpanno := GetStringValue(annuncio.Anno)
	if tmpanno != "0" && tmpanno != "" {
		tmp.Anno = &tmpanno
	}

	tmp.Prezzo = prezzoParse(annuncio.Prezzo)
	tmp.Classe = annuncio.Classe
	if annuncio.Mq != nil {
		replaced := strings.ReplaceAll(*annuncio.Mq, ",", "")
		tmp.Mq = &replaced
	}
	tmp.Piano = annuncio.Piano
	tmp.Piano_palazzo = annuncio.PianiCondominio
	tmp.Unita_condominio = annuncio.UnitaCondominio
	tmp.Numero_vani = annuncio.Vani
	tmp.Numero_camere = annuncio.Camere
	tmp.Numero_bagni = annuncio.Bagni
	tmp.Numero_balconi = annuncio.Balconi
	tmp.Numero_terrazzi = annuncio.Terrazzi
	tmp.Numero_box = annuncio.Box
	tmp.Numero_postiauto = annuncio.PostiAuto
}

func processImages(tmp *AnnuncioParsed, annuncio *AnnuncioXML) {
	path := filepath.Join(imgbasepath, tmp.Codice)
	err := os.MkdirAll(path, 0755)
	if err != nil {
		log.Printf("Failed to create directory: %v", err)
		panic(err)
	}
	var wg sync.WaitGroup
	sem := make(chan struct{}, concurrentImages) // Limit concurrent goroutines
	nfoto := 0
	var fotos []string
	if annuncio.Foto != nil {
		fotos = *annuncio.Foto
	}
	for _, url := range fotos {
		fmt.Println("Processing image", url)
		imgpath := filepath.Join(path, fmt.Sprintf("%d", nfoto))
		if _, err := os.Stat(imgpath); os.IsNotExist(err) {
			os.Mkdir(imgpath, 0755)
		}
		filename := filepath.Join(imgpath, fmt.Sprintf("%s_%d%s", tmp.Codice, nfoto, filepath.Ext(url)))
		wg.Add(1)
		sem <- struct{}{}
		go func(url, filename string) {
			defer wg.Done()
			defer func() { <-sem }()
			downloadImage(url, filename)
		}(url, filename)
		nfoto++
	}
	wg.Wait()
	processWebpImages(path)
}

func downloadImage(url, filename string) {
	response, err := http.Get(url)
	if err != nil {
		log.Fatalf("Failed to download image: %v", err)
		return
	}
	defer response.Body.Close()
	file, err := os.Create(filename)
	if err != nil {
		log.Fatalf("Failed to create file: %v", err)
		return
	}
	defer file.Close()
	_, err = io.Copy(file, response.Body)
	if err != nil {
		log.Fatalf("Failed to save image: %v", err)
	}
}

func processWebpImages(path string) {
	array, err := GetPathsArray(path)
	if err != nil {
		log.Fatal(err)
	}
	var wg sync.WaitGroup
	sem := make(chan struct{}, concurrentImages) // Limit concurrent goroutines
	for _, img := range array {
		wg.Add(1)
		sem <- struct{}{}
		go func(img string) {
			defer wg.Done()
			defer func() { <-sem }()
			err := Conversion(img)
			if err != nil {
				log.Fatal(err)
			}
		}(img)
	}
	wg.Wait()
}

func folderCleanup(codice string) error {
	folders := GetPathDirsArray(filepath.Join(imgbasepath, codice))
	non_webp := []string{}
	for _, folder := range folders {
		folderpath := filepath.Join(imgbasepath, codice, folder)
		files, err := os.ReadDir(folderpath)
		if err != nil {
			return err
		}
		for _, file := range files {
			if !strings.Contains(file.Name(), "webp") {
				non_webp = append(non_webp, filepath.Join(folderpath, file.Name()))
			}
		}
	}
	for _, file := range non_webp {
		err := os.Remove(file)
		if err != nil {
			return err
		}
	}
	return nil
}

func parseCaratteristiche(wrkrecord *AnnuncioXML) map[string]interface{} {
	if reflect.TypeOf(wrkrecord).Kind() != reflect.Ptr || reflect.ValueOf(wrkrecord).Elem().Kind() != reflect.Struct {
		return nil
	}
	tmpCaratteristiche := make(map[string]interface{})
	val := reflect.ValueOf(wrkrecord).Elem()
	numField := val.NumField()
	for i := 0; i < numField; i++ {
		if strings.HasPrefix(val.Type().Field(i).Name, "Scheda_") {
			nameField := val.Type().Field(i).Name
			valueField := val.Field(i)
			if valueField.IsValid() && !valueField.IsNil() {
				tmpCaratteristiche[nameField] = valueField.Interface()
			} else {
				tmpCaratteristiche[nameField] = nil
			}
		}
	}
	for _, scheda := range Miogest.GetCaratteristiche().Caratteristiche {
		if _, ok := tmpCaratteristiche[scheda.Tagxml]; !ok {
			tmpCaratteristiche[scheda.Tagxml] = nil
		}
	}
	return tmpCaratteristiche
}

var mesiMapping = map[int]string{
	1:  "Gennaio",
	2:  "Febbraio",
	3:  "Marzo",
	4:  "Aprile",
	5:  "Maggio",
	6:  "Giugno",
	7:  "Luglio",
	8:  "Agosto",
	9:  "Settembre",
	10: "Ottobre",
	11: "Novembre",
	12: "Dicembre",
}

func parseConsegna(original *string) *int {
	currentMonth := int(time.Now().Month())
	if original == nil {
		return nil
	}
	if *original == "Libero" {
		return &currentMonth
	} else {
		for k, v := range mesiMapping {
			if *original == v {
				return &k
			}
		}
	}
	return &currentMonth
}
