package sitemapgen

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
)

// XMLWriter grava urlsets de até URLsPerFile URLs e atualiza o índice.
type XMLWriter struct {
	siteURL     string
	outputDir   string
	urlsPerFile int
	fileIndex   int
	urlCount    int
	currentPath string
	file        *os.File
	completed   []string
}

func NewXMLWriter(siteURL, outputDir string, urlsPerFile int, resume *State) (*XMLWriter, error) {
	if err := os.MkdirAll(outputDir, 0o755); err != nil {
		return nil, err
	}
	w := &XMLWriter{
		siteURL:     siteURL,
		outputDir:   outputDir,
		urlsPerFile: urlsPerFile,
		completed:   nil,
	}
	if resume != nil {
		w.completed = append([]string{}, resume.CompletedFiles...)
		if resume.CurrentFile != "" && resume.URLsInCurrentFile > 0 {
			if err := w.resumeFile(outputDir, resume.CurrentFile, resume.URLsInCurrentFile); err != nil {
				return nil, err
			}
		} else {
			w.fileIndex = len(resume.CompletedFiles)
		}
	}
	return w, nil
}

func (w *XMLWriter) resumeFile(outputDir, name string, urlCount int) error {
	path := filepath.Join(outputDir, name)
	if err := truncateSitemapClosingTag(path); err != nil {
		return err
	}
	f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0o644)
	if err != nil {
		return err
	}
	w.file = f
	w.currentPath = name
	w.urlCount = urlCount
	if idx := strings.Index(name, "empresas-"); idx >= 0 {
		if n, err := strconv.Atoi(strings.TrimSuffix(name[idx+len("empresas-"):], ".xml")); err == nil {
			w.fileIndex = n
		}
	}
	return nil
}

func truncateSitemapClosingTag(path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	suffix := []byte(urlsetClose)
	if len(data) >= len(suffix) && bytes.Equal(data[len(data)-len(suffix):], suffix) {
		return os.Truncate(path, int64(len(data)-len(suffix)))
	}
	return nil
}

func (w *XMLWriter) TotalWritten() int64 {
	return int64(len(w.completed)*w.urlsPerFile) + int64(w.urlCount)
}

func (w *XMLWriter) CompletedFiles() []string {
	out := make([]string, len(w.completed))
	copy(out, w.completed)
	return out
}

func (w *XMLWriter) CurrentFileName() string {
	return w.currentPath
}

func (w *XMLWriter) URLsInCurrentFile() int {
	return w.urlCount
}

func (w *XMLWriter) Add(rec Record) error {
	if w.file == nil || w.urlCount >= w.urlsPerFile {
		if err := w.closeFile(); err != nil {
			return err
		}
		if err := w.openNextFile(); err != nil {
			return err
		}
	}
	loc := xmlEscape(rec.CanonicalURL(w.siteURL))
	line := "  <url><loc>" + loc + "</loc><changefreq>monthly</changefreq><priority>0.6</priority></url>\n"
	if _, err := w.file.WriteString(line); err != nil {
		return err
	}
	w.urlCount++
	return nil
}

func (w *XMLWriter) openNextFile() error {
	w.fileIndex++
	w.urlCount = 0
	w.currentPath = fmt.Sprintf("empresas-%06d.xml", w.fileIndex)
	path := filepath.Join(w.outputDir, w.currentPath)
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	w.file = f
	_, err = f.WriteString(xmlHeader + urlsetOpen)
	return err
}

func (w *XMLWriter) closeFile() error {
	if w.file == nil {
		return nil
	}
	if _, err := w.file.WriteString(urlsetClose); err != nil {
		_ = w.file.Close()
		return err
	}
	if err := w.file.Close(); err != nil {
		return err
	}
	w.file = nil
	if w.urlCount > 0 {
		w.completed = append(w.completed, w.currentPath)
	}
	return nil
}

func (w *XMLWriter) Finish() error {
	if err := w.closeFile(); err != nil {
		return err
	}
	return w.writeIndex()
}

func (w *XMLWriter) writeIndex() error {
	path := filepath.Join(w.outputDir, "sitemap-index.xml")
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	if _, err := f.WriteString(xmlHeader); err != nil {
		return err
	}
	if _, err := f.WriteString(`<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` + "\n"); err != nil {
		return err
	}
	for _, name := range w.completed {
		// Referência no índice (arquivo local; não é servido pelo site).
		loc := xmlEscape(w.siteURL + "/sitemaps/" + name)
		line := "  <sitemap><loc>" + loc + "</loc></sitemap>\n"
		if _, err := f.WriteString(line); err != nil {
			return err
		}
	}
	if _, err := f.WriteString("</sitemapindex>\n"); err != nil {
		return err
	}
	return nil
}

const (
	xmlHeader   = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
	urlsetOpen  = `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">` + "\n"
	urlsetClose = "</urlset>\n"
)

func xmlEscape(s string) string {
	s = strings.ReplaceAll(s, "&", "&amp;")
	s = strings.ReplaceAll(s, "<", "&lt;")
	s = strings.ReplaceAll(s, ">", "&gt;")
	s = strings.ReplaceAll(s, "'", "&apos;")
	s = strings.ReplaceAll(s, "\"", "&quot;")
	return s
}
