// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================

package contact

import (
	"billionmail-core/internal/model/entity"
	"bytes"
	"encoding/csv"
	"fmt"
	"github.com/gogf/gf/crypto/gmd5"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/xuri/excelize/v2"
	"math/rand"
	"strings"
	"time"
)

const (
	FileTypeCSV   = "csv"
	FileTypeExcel = "excel"
	FileTypeTXT   = "txt"
)

// Status constants
const (
	StatusSubscribe   = "subscribe"
	StatusUnsubscribe = "unsubscribe"
)

const (
	DefaultPage     = 1
	DefaultPageSize = 10
	MaxPageSize     = 100
)

// parseContactFile parses contact file content based on file type
func parseContactFile(fileContent []byte, fileType string) ([]*entity.Contact, error) {

	// if txt format, split by line
	if fileType == "txt" {
		return parseTXTFile(fileContent)
	}
	// CSV format
	if fileType == "csv" {
		return parseCSVFile(fileContent)
	}
	// Excel format
	if fileType == "excel" {
		return parseExcelFile(fileContent)
	}
	return nil, fmt.Errorf("unsupported file type: %s", fileType)
}

// parseTXTFile parses txt file content, one email per line
func parseTXTFile(fileContent []byte) ([]*entity.Contact, error) {
	//fmt.Printf("parseTXTFile - Content length: %d bytes\n", len(fileContent))
	//fmt.Printf("parseTXTFile - Content as string: %s\n", string(fileContent))

	var contacts []*entity.Contact
	lines := bytes.Split(fileContent, []byte("\n"))

	for _, line := range lines {
		// remove whitespace characters
		email := strings.TrimSpace(string(line))

		if email == "" {
			continue
		}

		// validate email format
		if !validateEmail(email) {
			fmt.Printf("parseTXTFile - Invalid email format: %s\n", email)
			continue
		}

		contacts = append(contacts, &entity.Contact{
			Email:  email,
			Active: 1, // default subscription status
		})
	}

	//fmt.Printf("parseTXTFile - Total valid contacts: %d\n", len(contacts))
	return contacts, nil
}

// parseCSVFile parses CSV file content
func parseCSVFile(fileContent []byte) ([]*entity.Contact, error) {
	// handle possible BOM
	fileContent = bytes.TrimPrefix(fileContent, []byte("\xef\xbb\xbf"))

	reader := csv.NewReader(bytes.NewReader(fileContent))
	reader.FieldsPerRecord = -1 // Allow different number of fields per line
	reader.TrimLeadingSpace = true
	reader.LazyQuotes = true  // Allow non-strict quotes
	reader.ReuseRecord = true // Reuse record to improve performance

	records, err := reader.ReadAll()
	if err != nil {
		return nil, fmt.Errorf("error reading CSV file: %v", err)
	}

	var contacts []*entity.Contact
	var hasHeader bool

	// Check if header exists
	if len(records) > 0 {
		firstRow := records[0]
		if len(firstRow) >= 1 && strings.ToLower(firstRow[0]) == "email" {
			hasHeader = true
		}
	}

	// Process records
	startIndex := 0
	if hasHeader {
		startIndex = 1
	}

	for i := startIndex; i < len(records); i++ {
		record := records[i]
		if len(record) == 0 {
			continue
		}

		email := strings.TrimSpace(record[0])
		if email == "" {
			continue
		}

		// validate email format
		if !validateEmail(email) {
			continue
		}

		active := 1 // Default to active
		if len(record) > 1 {
			if status := strings.TrimSpace(record[1]); status == "0" {
				active = 0
			}
		}

		contacts = append(contacts, &entity.Contact{
			Email:  email,
			Active: active,
		})
	}

	return contacts, nil
}

// validateEmail validates email format
func validateEmail(email string) bool {
	// simple email format validation
	return strings.Contains(email, "@") && strings.Contains(email, ".")
}

// exportContactFile Export contact file
func exportContactFile(contacts []*entity.Contact, fileType string) ([]byte, error) {
	switch strings.ToLower(fileType) {
	case FileTypeCSV:
		return exportToCSV(contacts)
	case FileTypeExcel:
		return exportToExcel(contacts)
	case FileTypeTXT:
		return exportToTXT(contacts)
	default:
		return nil, gerror.New("Unsupported file format")
	}
}

// exportToTXT Export to TXT file
func exportToTXT(contacts []*entity.Contact) ([]byte, error) {
	var lines []string
	for _, contact := range contacts {
		status := StatusSubscribe
		if contact.Active == 0 {
			status = StatusUnsubscribe
		}
		lines = append(lines, fmt.Sprintf("%s,%s", contact.Email, status))
	}
	return []byte(strings.Join(lines, "\n")), nil
}

// parseExcelFile Parse Excel file
func parseExcelFile(fileContent []byte) ([]*entity.Contact, error) {
	// Create Excel file from byte data
	f, err := excelize.OpenReader(bytes.NewReader(fileContent))
	if err != nil {
		return nil, err
	}
	defer f.Close()

	// Get first sheet
	sheets := f.GetSheetList()
	if len(sheets) == 0 {
		return nil, gerror.New("Excel file has no sheets")
	}

	rows, err := f.GetRows(sheets[0])
	if err != nil {
		return nil, err
	}

	var contacts []*entity.Contact
	var emailCol, statusCol int
	var hasHeader bool

	// Check for header and determine column positions
	if len(rows) > 0 {
		for i, cell := range rows[0] {
			switch strings.ToLower(strings.TrimSpace(cell)) {
			case "email":
				emailCol = i
				hasHeader = true
			case "status":
				statusCol = i
				hasHeader = true
			}
		}
	}

	// If email column not found, assume first column is email
	if !hasHeader {
		emailCol = 0
	}

	startIndex := 0
	if hasHeader {
		startIndex = 1
	}

	for i := startIndex; i < len(rows); i++ {
		row := rows[i]
		if len(row) <= emailCol {
			continue
		}

		email := strings.TrimSpace(row[emailCol])
		if email == "" {
			continue
		}

		contact := &entity.Contact{
			Email:  email,
			Active: 1,
		}

		if statusCol < len(row) {
			status := strings.ToLower(strings.TrimSpace(row[statusCol]))
			switch status {
			case "0", StatusUnsubscribe:
				contact.Active = 0
			}
		}

		if !isValidEmail(contact.Email) {
			continue
		}

		contacts = append(contacts, contact)
	}

	return contacts, nil
}

// isValidEmail Validate email format
func isValidEmail(email string) bool {

	if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
		return false
	}

	parts := strings.Split(email, "@")
	if len(parts) != 2 {
		return false
	}

	localPart := parts[0]
	domain := parts[1]

	if localPart == "" || domain == "" {
		return false
	}

	if !strings.Contains(domain, ".") {
		return false
	}

	if len(email) > 254 {
		return false
	}

	return true
}

// exportToCSV Export contacts to CSV file
func exportToCSV(contacts []*entity.Contact) ([]byte, error) {
	buf := new(bytes.Buffer)
	writer := csv.NewWriter(buf)

	// header
	err := writer.Write([]string{"Email", "Status"})
	if err != nil {
		return nil, err
	}

	// data
	for _, contact := range contacts {
		status := StatusSubscribe
		if contact.Active == 0 {
			status = StatusUnsubscribe
		}

		err := writer.Write([]string{
			contact.Email,
			status,
		})
		if err != nil {
			return nil, err
		}
	}

	writer.Flush()
	if err := writer.Error(); err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// exportToExcel Export contacts to Excel file
func exportToExcel(contacts []*entity.Contact) ([]byte, error) {
	f := excelize.NewFile()
	defer f.Close()

	// Create header
	f.SetCellValue("Sheet1", "A1", "Email")
	f.SetCellValue("Sheet1", "B1", "Status")

	// Write data
	for i, contact := range contacts {
		row := i + 2 // Start writing data from second row
		status := StatusSubscribe
		if contact.Active == 0 {
			status = StatusUnsubscribe
		}

		f.SetCellValue("Sheet1", fmt.Sprintf("A%d", row), contact.Email)
		f.SetCellValue("Sheet1", fmt.Sprintf("B%d", row), status)
	}

	// Set column width
	f.SetColWidth("Sheet1", "A", "A", 30)
	f.SetColWidth("Sheet1", "B", "B", 15)
	f.SetColWidth("Sheet1", "C", "C", 20)

	// Export to byte array
	buf, err := f.WriteToBuffer()
	if err != nil {
		return nil, err
	}

	return buf.Bytes(), nil
}

// GfMd5Short generates a short MD5 hash based on the current time and a random number
func GfMd5Short() string {
	str := fmt.Sprintf("%s_%d_%d", time.Now().UnixNano(), rand.Intn(100000))
	return gmd5.MustEncryptString(str)[:12]
}
