package main

import (
	"backend/config"
	"backend/parser"
	"backend/typesdefs"
	"backend/utils"
	"fmt"
	"log"
	"reflect"
	"regexp"
	"slices"
	"strconv"
	"strings"
	"time"
)

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

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

// ANNUNCI FROM MIOGEST
func (m *MiogestHandler) GetAnnunci() (typesdefs.AnnunciXML, error) {
	if len(m.AnnunciXML.Annuncio) == 0 || time.Now().After(m.annunci_cache_expires) {
		err := m.GetAnnunciFromMiogest()
		if err != nil {
			return typesdefs.AnnunciXML{}, err
		}
	}
	return m.AnnunciXML, nil
}

func (m *MiogestHandler) GetAnnunciFromMiogest() error {
	maxRetries := 3
	baseDelay := 10 * time.Second

	for attempt := range maxRetries {
		var err error
		m.AnnunciXML, err = utils.GetDataXML[typesdefs.AnnunciXML]("http://partner.miogest.com/agenzie/infoalloggi.xml")

		if err == nil {
			// Success - validate we got data
			if len(m.AnnunciXML.Annuncio) > 0 {
				m.annunci_cache_expires = time.Now().Add(1 * time.Hour)
				return nil
			}
			log.Printf("Warning: Successfully fetched XML but got 0 annunci")
		}

		// Log the specific error type
		if err != nil {
			log.Printf("Failed to get annunci (attempt %d/%d): %v", attempt+1, maxRetries, err)
		}

		// Failed - check if we should retry
		if attempt < maxRetries-1 {
			delay := baseDelay * time.Duration(1<<attempt) // Exponential backoff
			log.Printf("Failed to get annunci (attempt %d/%d): %v. Retrying in %v...",
				attempt+1, maxRetries, err, delay)
			time.Sleep(delay)
		} else {

			log.Printf("Failed to get annunci after %d attempts: %v", maxRetries, err)
			return fmt.Errorf("failed to get annunci after %d attempts: %w", maxRetries, err)
		}
	}
	return nil
}

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

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

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

func (m *MiogestHandler) GenerateCaratteristiche() {
	//https://www.miogest.com/apps/revo.aspx?tipo=schede
	var result, err = utils.GetXMLFromFile[typesdefs.DefaultCaratteristiche]("./caratteristiche.xml")
	if err != nil {
		log.Printf("Failed to get caratteristiche: %v", err)
		return
	}
	var array typesdefs.ListaCaratteristiche
	for _, tag := range result.Scheda {
		var c = typesdefs.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() {
	//https://www.miogest.com/apps/revo.aspx?tipo=categorie
	var result, err = utils.GetXMLFromFile[typesdefs.DefaultCategories]("./categorie.xml")
	if err != nil {
		log.Printf("Failed to get categorie: %v", err)
		return
	}
	var listaCategorie []typesdefs.Categoria
	for _, elem := range result.Cat {
		var c = typesdefs.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(imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess, forceMediaStale bool) (typesdefs.AnnunciParsed, error) {
	// salvo il parsing nello struct nel caso voglio implementare un caching e non fare il parsing ad ogni chiamata
	var err error
	m.AnnunciParsed, err = m.ParseAnnunci("", imgPool, videoPool, forceMediaStale)
	return m.AnnunciParsed, err
}

// versione per singolo annuncio
func (m *MiogestHandler) GetAnnuncioParsed(codFilter string, imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess) (typesdefs.AnnunciParsed, error) {

	data, err := m.ParseAnnunci(codFilter, imgPool, videoPool, false)
	if err != nil {
		return typesdefs.AnnunciParsed{}, err
	}
	if len(data.Annuncio) == 0 {
		return typesdefs.AnnunciParsed{}, fmt.Errorf("No Annuncio found for codice %s", codFilter)
	}
	return data, nil

}

func (m *MiogestHandler) ParseAnnunci(codFilter string, imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess, forceMediaStale bool) (typesdefs.AnnunciParsed, error) {
	annunci, err := m.GetAnnunci()
	if err != nil {
		log.Fatalf("Failed to get annunci for parsing: %v", err.Error())
		return typesdefs.AnnunciParsed{}, err
	}

	if len(annunci.Annuncio) == 0 {
		return typesdefs.AnnunciParsed{}, fmt.Errorf("No Annunci in update")
	}

	if codFilter != "" {
		idx := slices.IndexFunc(annunci.Annuncio, func(a typesdefs.AnnuncioXML) bool {
			return *a.Codice == codFilter
		})
		if idx == -1 {
			return typesdefs.AnnunciParsed{}, fmt.Errorf("Codice %s not found", codFilter)
		}
		annunci.Annuncio = []typesdefs.AnnuncioXML{annunci.Annuncio[idx]}
	}

	image_codes_to_process := []string{}
	if config.Cfg.Images.Enabled {
		var err error
		if forceMediaStale {
			// process all images
			image_codes_to_process = make([]string, 0, len(annunci.Annuncio))
			for _, annuncio := range annunci.Annuncio {
				if annuncio.Codice != nil {
					image_codes_to_process = append(image_codes_to_process, *annuncio.Codice)
				}
			}
		} else {
			// process only new or updated images
			image_codes_to_process, err = CodiciToProcessImages(&annunci)
			if err != nil {
				log.Fatalf("Failed to find images to update: %v", err.Error())
			}
		}
		if codFilter != "" {
			// if processing single codice, ensure it's in the list
			if !slices.Contains(image_codes_to_process, codFilter) {
				image_codes_to_process = append(image_codes_to_process, codFilter)
			}
		}
		err = ReconcileImages()
		err = ForceClearImages(image_codes_to_process)
		if err != nil {
			log.Fatalf("Failed to clear images for codice %s: %v", codFilter, err.Error())
		}

	}
	video_codes_to_process := []string{}
	if config.Cfg.Videos.Enabled {
		var err error
		if forceMediaStale {
			// process all videos
			video_codes_to_process = make([]string, 0, len(annunci.Annuncio))
			for _, annuncio := range annunci.Annuncio {
				if annuncio.Codice != nil {
					video_codes_to_process = append(video_codes_to_process, *annuncio.Codice)
				}
			}

		} else {

			// process only new or updated videos
			video_codes_to_process, err = CodiciToProcessVideos(&annunci)
			if err != nil {
				log.Fatalf("Failed to find videos to update: %v", err.Error())
			}

		}
		if codFilter != "" {
			// if processing single codice, ensure it's in the list
			if !slices.Contains(video_codes_to_process, codFilter) {
				video_codes_to_process = append(video_codes_to_process, codFilter)
			}
		}
		err = ReconcileVideos()
		err = ForceClearVideos(video_codes_to_process)
		if err != nil {
			log.Fatalf("Failed to clear videos for codice %s: %v", codFilter, err.Error())
		}

	}

	totalAnnunci := len(annunci.Annuncio)
	log.Printf("Processing %d annunci (images to update: %d, videos to update: %d)", totalAnnunci, len(image_codes_to_process), len(video_codes_to_process))
	var AnnunciArray typesdefs.AnnunciParsed
	AnnunciArray.Annuncio = make([]typesdefs.AnnuncioParsed, 0, len(annunci.Annuncio))
	m.InitDefaults()

	for i := range annunci.Annuncio {
		imgToUpdate := slices.Contains(image_codes_to_process, *annunci.Annuncio[i].Codice)
		updateVideos := slices.Contains(video_codes_to_process, *annunci.Annuncio[i].Codice)
		result := extractData_update(&annunci.Annuncio[i], imgToUpdate, updateVideos, m, imgPool, videoPool)
		AnnunciArray.Annuncio = append(AnnunciArray.Annuncio, result)

	}
	log.Printf("Processed %d annunci", len(AnnunciArray.Annuncio))
	fmt.Println()
	return AnnunciArray, nil
}

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

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

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

func extractData_update(annuncio *typesdefs.AnnuncioXML, updateImages bool, updateVideos bool, m *MiogestHandler, imgPool *[]typesdefs.MediaToProcess, videoPool *[]typesdefs.MediaToProcess) typesdefs.AnnuncioParsed {
	var tmp typesdefs.AnnuncioParsed = typesdefs.AnnuncioParsed{}
	tmp.Codice = utils.RemoveWhitespace(utils.GetStringValue(annuncio.Codice))
	processLocatore(&tmp, annuncio)
	processIndirizzo(&tmp, annuncio)

	tmp.Tipo = parser.ParseTipologia(annuncio.Tipologia, annuncio.Tipologia2)
	tmp.Consegna = parser.ParseConsegna(annuncio.Consegna)
	processCategorie(&tmp, annuncio, m)
	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

	if annuncio.Dettaglio != nil {
		disp, parm, pers, err := parser.ParseDettaglio(*annuncio.Dettaglio, tmp.Consegna)
		if err == nil {
			tmp.Disponibile_da = disp
			tmp.Permanenza = parm
			tmp.Persone = pers

		}
	}

	tmp.Web = boolUpd(true)

	if updateVideos {
		AddToVideoProcessing(&tmp, annuncio, videoPool)
	}

	if annuncio.Video != nil {
		ext_videos := []string{}
		for _, v := range *annuncio.Video {
			if strings.Contains(v, "youtu") {
				ext_videos = append(ext_videos, v)
			}
			tmp.External_videos = &ext_videos
		}
	} else {
		tmp.External_videos = &[]string{}
	}

	if annuncio.Accessori != nil {
		tmp.Accessori = annuncio.Accessori
	}
	tmpcaratt := parseCaratteristiche(annuncio, m)
	tmp.Caratteristiche = &tmpcaratt

	if updateImages {
		AddToImageProcessing(&tmp, annuncio, imgPool)
	}

	return tmp
}

func flatClienteTipologia(t []string) string {
	if len(t) == 0 {
		return ""
	}
	if slices.Contains(t, "Proprietario") {
		return "proprietario"
	} else if slices.Contains(t, "Delegato") {
		return "delegato"
	} else if slices.Contains(t, "Cliente") {
		return "inquilino"
	}
	return ""
}
func tipologiaPriority(tipologie []string) int {
	if slices.Contains(tipologie, "Delegato") {
		return 3
	}
	if slices.Contains(tipologie, "Cliente") {
		return 2
	}
	if slices.Contains(tipologie, "Proprietario") {
		return 1
	}
	return 0
}
func selectTargetCliente(annuncio *typesdefs.AnnuncioXML) (int, bool) {
	if len(annuncio.Clienti) == 0 {
		return 0, false
	}
	best := 0
	bestPriority := tipologiaPriority(annuncio.Clienti[0].Tipologie)
	for i := 1; i < len(annuncio.Clienti); i++ {
		if p := tipologiaPriority(annuncio.Clienti[i].Tipologie); p > bestPriority {
			bestPriority = p
			best = i
		}
	}
	return best, true
}

func processLocatore(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML) {
	idx, ok := selectTargetCliente(annuncio)
	if !ok {
		log.Printf("Annuncio %s has no client information", utils.GetStringValue(annuncio.Codice))
		return
	}
	trg := annuncio.Clienti[idx]

	tmp.Email = parser.ParseEmail(trg.Email1)

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

func processIndirizzo(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML) {
	tmp.Indirizzo = strUpd(utils.MakeUppercase(utils.GetStringValue(annuncio.Indirizzo)))
	tmp.Civico = annuncio.Civico
	tmp.Comune = strUpd(utils.MakeUppercase(utils.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(utils.MakeUppercase(utils.GetStringValue(annuncio.IndirizzoSecondario)))
	tmp.Civico_secondario = annuncio.CivicoSecondario
	tmp.Lat_secondario = annuncio.LatitudineSecondario
	tmp.Lon_secondario = annuncio.LongitudineSecondario
}

func processCategorie(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML, m *MiogestHandler) {
	if annuncio.Categoria == nil {
		return
	}
	resultArray := make([]string, 0)
	cats := *annuncio.Categoria
	defaultCategorie := m.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 processDatiImmobile(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML) {
	tmpanno := utils.GetStringValue(annuncio.Anno)
	if tmpanno != "0" && tmpanno != "" {
		tmp.Anno = &tmpanno
	}

	tmp.Prezzo = parser.ParsePrezzo(annuncio.Prezzo)
	tmp.Classe = annuncio.Classe
	if annuncio.Mq != nil {
		replaced := strings.ReplaceAll(*annuncio.Mq, ",", ".")
		if s, err := strconv.ParseFloat(replaced, 64); err == nil {
			tmp.Mq = &s
		}

	}
	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 parseCaratteristiche(wrkrecord *typesdefs.AnnuncioXML, m *MiogestHandler) 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 m.GetCaratteristiche().Caratteristiche {
		if _, ok := tmpCaratteristiche[scheda.Tagxml]; !ok {
			tmpCaratteristiche[scheda.Tagxml] = nil
		}
	}
	return tmpCaratteristiche
}
