package services

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"time"

	"git.mchus.pro/mchus/quoteforge/internal/localdb"
	"git.mchus.pro/mchus/quoteforge/internal/models"
	"git.mchus.pro/mchus/quoteforge/internal/repository"
	"github.com/google/uuid"
	"gorm.io/gorm"
)

type VendorWorkspaceImportResult struct {
	Imported int                             `json:"imported"`
	Project  *models.Project                 `json:"project,omitempty"`
	Configs  []VendorWorkspaceImportedConfig `json:"configs"`
}

type VendorWorkspaceImportedConfig struct {
	UUID        string `json:"uuid"`
	Name        string `json:"name"`
	ServerCount int    `json:"server_count"`
	ServerModel string `json:"server_model,omitempty"`
	Rows        int    `json:"rows"`
}

type importedWorkspace struct {
	SourceFormat   string
	SourceDocID    string
	SourceFileName string
	CurrencyCode   string
	Configurations []importedConfiguration
}

type importedConfiguration struct {
	GroupID      string
	Name         string
	Line         int
	ServerCount  int
	ServerModel  string
	Article      string
	CurrencyCode string
	Rows         []localdb.VendorSpecItem
	TotalPrice   *float64
}

type groupedItem struct {
	order int
	row   cfxmlProductLineItem
}

type cfxmlDocument struct {
	XMLName                xml.Name                `xml:"CFXML"`
	ThisDocumentIdentifier cfxmlDocumentIdentifier `xml:"thisDocumentIdentifier"`
	CFData                 cfxmlData               `xml:"CFData"`
}

type cfxmlDocumentIdentifier struct {
	ProprietaryDocumentIdentifier string `xml:"ProprietaryDocumentIdentifier"`
}

type cfxmlData struct {
	ProprietaryInformation []cfxmlProprietaryInformation `xml:"ProprietaryInformation"`
	ProductLineItems       []cfxmlProductLineItem        `xml:"ProductLineItem"`
}

type cfxmlProprietaryInformation struct {
	Name  string `xml:"Name"`
	Value string `xml:"Value"`
}

type cfxmlProductLineItem struct {
	ProductLineNumber                     string                     `xml:"ProductLineNumber"`
	ItemNo                                string                     `xml:"ItemNo"`
	TransactionType                       string                     `xml:"TransactionType"`
	ProprietaryGroupIdentifier            string                     `xml:"ProprietaryGroupIdentifier"`
	ConfigurationGroupLineNumberReference string                     `xml:"ConfigurationGroupLineNumberReference"`
	Quantity                              string                     `xml:"Quantity"`
	ProductIdentification                 cfxmlProductIdentification `xml:"ProductIdentification"`
	UnitListPrice                         cfxmlUnitListPrice         `xml:"UnitListPrice"`
	ProductSubLineItems                   []cfxmlProductSubLineItem  `xml:"ProductSubLineItem"`
}

type cfxmlProductSubLineItem struct {
	LineNumber            string                     `xml:"LineNumber"`
	TransactionType       string                     `xml:"TransactionType"`
	Quantity              string                     `xml:"Quantity"`
	ProductIdentification cfxmlProductIdentification `xml:"ProductIdentification"`
	UnitListPrice         cfxmlUnitListPrice         `xml:"UnitListPrice"`
}

type cfxmlProductIdentification struct {
	PartnerProductIdentification cfxmlPartnerProductIdentification `xml:"PartnerProductIdentification"`
}

type cfxmlPartnerProductIdentification struct {
	ProprietaryProductIdentifier string `xml:"ProprietaryProductIdentifier"`
	ProprietaryProductChar       string `xml:"ProprietaryProductChar"`
	ProductCharacter             string `xml:"ProductCharacter"`
	ProductDescription           string `xml:"ProductDescription"`
	ProductName                  string `xml:"ProductName"`
	ProductTypeCode              string `xml:"ProductTypeCode"`
}

type cfxmlUnitListPrice struct {
	FinancialAmount cfxmlFinancialAmount `xml:"FinancialAmount"`
}

type cfxmlFinancialAmount struct {
	GlobalCurrencyCode string `xml:"GlobalCurrencyCode"`
	MonetaryAmount     string `xml:"MonetaryAmount"`
}

func (s *LocalConfigurationService) ImportVendorWorkspaceToProject(projectUUID string, sourceFileName string, data []byte, ownerUsername string) (*VendorWorkspaceImportResult, error) {
	project, err := s.localDB.GetProjectByUUID(projectUUID)
	if err != nil {
		return nil, ErrProjectNotFound
	}

	workspace, err := parseCFXMLWorkspace(data, filepath.Base(sourceFileName))
	if err != nil {
		return nil, err
	}

	result := &VendorWorkspaceImportResult{
		Imported: 0,
		Project:  localdb.LocalToProject(project),
		Configs:  make([]VendorWorkspaceImportedConfig, 0, len(workspace.Configurations)),
	}

	err = s.localDB.DB().Transaction(func(tx *gorm.DB) error {
		bookRepo := repository.NewPartnumberBookRepository(tx)
		for _, imported := range workspace.Configurations {
			now := time.Now()
			cfgUUID := uuid.NewString()
			groupRows, items, totalPrice, estimatePricelistID, err := s.prepareImportedConfiguration(imported.Rows, imported.ServerCount, bookRepo)
			if err != nil {
				return fmt.Errorf("prepare imported configuration group %s: %w", imported.GroupID, err)
			}
			localCfg := &localdb.LocalConfiguration{
				UUID:             cfgUUID,
				ProjectUUID:      &projectUUID,
				IsActive:         true,
				Name:             imported.Name,
				Items:            items,
				TotalPrice:       totalPrice,
				ServerCount:      imported.ServerCount,
				ServerModel:      imported.ServerModel,
				Article:          imported.Article,
				PricelistID:      estimatePricelistID,
				VendorSpec:       groupRows,
				CreatedAt:        now,
				UpdatedAt:        now,
				SyncStatus:       "pending",
				OriginalUsername: ownerUsername,
			}

			if err := s.createWithVersionTx(tx, localCfg, ownerUsername); err != nil {
				return fmt.Errorf("import configuration group %s: %w", imported.GroupID, err)
			}

			result.Imported++
			result.Configs = append(result.Configs, VendorWorkspaceImportedConfig{
				UUID:        localCfg.UUID,
				Name:        localCfg.Name,
				ServerCount: localCfg.ServerCount,
				ServerModel: localCfg.ServerModel,
				Rows:        len(localCfg.VendorSpec),
			})
		}
		return nil
	})
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (s *LocalConfigurationService) prepareImportedConfiguration(rows []localdb.VendorSpecItem, serverCount int, bookRepo *repository.PartnumberBookRepository) (localdb.VendorSpec, localdb.LocalConfigItems, *float64, *uint, error) {
	resolver := NewVendorSpecResolver(bookRepo)
	resolved, err := resolver.Resolve(append([]localdb.VendorSpecItem(nil), rows...))
	if err != nil {
		return nil, nil, nil, nil, err
	}

	canonical := make(localdb.VendorSpec, 0, len(resolved))
	for _, row := range resolved {
		if len(row.LotMappings) == 0 && strings.TrimSpace(row.ResolvedLotName) != "" {
			row.LotMappings = []localdb.VendorSpecLotMapping{
				{LotName: strings.TrimSpace(row.ResolvedLotName), QuantityPerPN: 1},
			}
		}
		row.LotMappings = normalizeImportedLotMappings(row.LotMappings)
		row.ResolvedLotName = ""
		row.ResolutionSource = ""
		row.ManualLotSuggestion = ""
		row.LotQtyPerPN = 0
		row.LotAllocations = nil
		canonical = append(canonical, row)
	}

	estimatePricelist, _ := s.localDB.GetLatestLocalPricelistBySource("estimate")
	var serverPricelistID *uint
	if estimatePricelist != nil {
		serverPricelistID = &estimatePricelist.ServerID
	}

	items := aggregateVendorSpecToItems(canonical, estimatePricelist, s.localDB)
	totalValue := items.Total()
	if serverCount > 1 {
		totalValue *= float64(serverCount)
	}
	totalPrice := &totalValue
	return canonical, items, totalPrice, serverPricelistID, nil
}

func aggregateVendorSpecToItems(spec localdb.VendorSpec, estimatePricelist *localdb.LocalPricelist, local *localdb.LocalDB) localdb.LocalConfigItems {
	if len(spec) == 0 {
		return localdb.LocalConfigItems{}
	}

	lotMap := make(map[string]int)
	order := make([]string, 0)
	for _, row := range spec {
		for _, mapping := range normalizeImportedLotMappings(row.LotMappings) {
			if _, exists := lotMap[mapping.LotName]; !exists {
				order = append(order, mapping.LotName)
			}
			lotMap[mapping.LotName] += row.Quantity * mapping.QuantityPerPN
		}
	}

	sort.Strings(order)
	items := make(localdb.LocalConfigItems, 0, len(order))
	for _, lotName := range order {
		unitPrice := 0.0
		if estimatePricelist != nil && local != nil {
			if price, err := local.GetLocalPriceForLot(estimatePricelist.ID, lotName); err == nil && price > 0 {
				unitPrice = price
			}
		}
		items = append(items, localdb.LocalConfigItem{
			LotName:   lotName,
			Quantity:  lotMap[lotName],
			UnitPrice: unitPrice,
		})
	}
	return items
}

func normalizeImportedLotMappings(in []localdb.VendorSpecLotMapping) []localdb.VendorSpecLotMapping {
	if len(in) == 0 {
		return nil
	}
	merged := make(map[string]int, len(in))
	order := make([]string, 0, len(in))
	for _, mapping := range in {
		lot := strings.TrimSpace(mapping.LotName)
		if lot == "" {
			continue
		}
		qty := mapping.QuantityPerPN
		if qty < 1 {
			qty = 1
		}
		if _, exists := merged[lot]; !exists {
			order = append(order, lot)
		}
		merged[lot] += qty
	}
	out := make([]localdb.VendorSpecLotMapping, 0, len(order))
	for _, lot := range order {
		out = append(out, localdb.VendorSpecLotMapping{
			LotName:       lot,
			QuantityPerPN: merged[lot],
		})
	}
	if len(out) == 0 {
		return nil
	}
	return out
}

func parseCFXMLWorkspace(data []byte, sourceFileName string) (*importedWorkspace, error) {
	var doc cfxmlDocument
	if err := xml.Unmarshal(data, &doc); err != nil {
		return nil, fmt.Errorf("parse CFXML workspace: %w", err)
	}
	if doc.XMLName.Local != "CFXML" {
		return nil, fmt.Errorf("unsupported workspace root: %s", doc.XMLName.Local)
	}
	if len(doc.CFData.ProductLineItems) == 0 {
		return nil, fmt.Errorf("CFXML workspace has no ProductLineItem rows")
	}

	workspace := &importedWorkspace{
		SourceFormat:   "CFXML",
		SourceDocID:    strings.TrimSpace(doc.ThisDocumentIdentifier.ProprietaryDocumentIdentifier),
		SourceFileName: sourceFileName,
		CurrencyCode:   detectWorkspaceCurrency(doc.CFData.ProprietaryInformation, doc.CFData.ProductLineItems),
	}

	type groupBucket struct {
		order int
		items []groupedItem
	}

	groupOrder := make([]string, 0)
	groups := make(map[string]*groupBucket)
	for idx, item := range doc.CFData.ProductLineItems {
		groupID := strings.TrimSpace(item.ProprietaryGroupIdentifier)
		if groupID == "" {
			groupID = firstNonEmpty(strings.TrimSpace(item.ProductLineNumber), strings.TrimSpace(item.ItemNo), fmt.Sprintf("group-%d", idx+1))
		}
		bucket := groups[groupID]
		if bucket == nil {
			bucket = &groupBucket{order: idx}
			groups[groupID] = bucket
			groupOrder = append(groupOrder, groupID)
		}
		bucket.items = append(bucket.items, groupedItem{order: idx, row: item})
	}

	for lineIdx, groupID := range groupOrder {
		bucket := groups[groupID]
		if bucket == nil || len(bucket.items) == 0 {
			continue
		}
		primary := pickPrimaryTopLevelRow(bucket.items)
		serverCount := maxInt(parseInt(primary.row.Quantity), 1)
		rows := make([]localdb.VendorSpecItem, 0, len(bucket.items)*4)
		sortOrder := 10

		for _, item := range bucket.items {
			topRow := vendorSpecItemFromTopLevel(item.row, serverCount, sortOrder)
			if topRow != nil {
				rows = append(rows, *topRow)
				sortOrder += 10
			}

			for _, sub := range item.row.ProductSubLineItems {
				subRow := vendorSpecItemFromSubLine(sub, sortOrder)
				if subRow == nil {
					continue
				}
				rows = append(rows, *subRow)
				sortOrder += 10
			}
		}

		total := sumVendorSpecRows(rows, serverCount)
		name := strings.TrimSpace(primary.row.ProductIdentification.PartnerProductIdentification.ProductName)
		if name == "" {
			name = strings.TrimSpace(primary.row.ProductIdentification.PartnerProductIdentification.ProductDescription)
		}
		if name == "" {
			name = fmt.Sprintf("Imported config %d", lineIdx+1)
		}

		workspace.Configurations = append(workspace.Configurations, importedConfiguration{
			GroupID:      groupID,
			Name:         name,
			Line:         (lineIdx + 1) * 10,
			ServerCount:  serverCount,
			ServerModel:  strings.TrimSpace(primary.row.ProductIdentification.PartnerProductIdentification.ProductDescription),
			Article:      strings.TrimSpace(primary.row.ProductIdentification.PartnerProductIdentification.ProprietaryProductIdentifier),
			CurrencyCode: workspace.CurrencyCode,
			Rows:         rows,
			TotalPrice:   total,
		})
	}

	if len(workspace.Configurations) == 0 {
		return nil, fmt.Errorf("CFXML workspace has no importable configuration groups")
	}

	return workspace, nil
}

func detectWorkspaceCurrency(meta []cfxmlProprietaryInformation, rows []cfxmlProductLineItem) string {
	for _, item := range meta {
		if strings.EqualFold(strings.TrimSpace(item.Name), "Currencies") {
			value := strings.TrimSpace(item.Value)
			if value != "" {
				return value
			}
		}
	}
	for _, row := range rows {
		code := strings.TrimSpace(row.UnitListPrice.FinancialAmount.GlobalCurrencyCode)
		if code != "" {
			return code
		}
	}
	return ""
}

func pickPrimaryTopLevelRow(items []groupedItem) groupedItem {
	best := items[0]
	bestScore := primaryScore(best.row)
	for _, item := range items[1:] {
		score := primaryScore(item.row)
		if score > bestScore {
			best = item
			bestScore = score
			continue
		}
		if score == bestScore && compareLineNumbers(item.row.ProductLineNumber, best.row.ProductLineNumber) < 0 {
			best = item
		}
	}
	return best
}

func primaryScore(row cfxmlProductLineItem) int {
	score := len(row.ProductSubLineItems)
	if strings.EqualFold(strings.TrimSpace(row.ProductIdentification.PartnerProductIdentification.ProductTypeCode), "Hardware") {
		score += 100000
	}
	return score
}

func compareLineNumbers(left, right string) int {
	li := parseInt(left)
	ri := parseInt(right)
	switch {
	case li < ri:
		return -1
	case li > ri:
		return 1
	default:
		return strings.Compare(left, right)
	}
}

func vendorSpecItemFromTopLevel(item cfxmlProductLineItem, serverCount int, sortOrder int) *localdb.VendorSpecItem {
	code := strings.TrimSpace(item.ProductIdentification.PartnerProductIdentification.ProprietaryProductIdentifier)
	desc := strings.TrimSpace(item.ProductIdentification.PartnerProductIdentification.ProductDescription)
	if code == "" && desc == "" {
		return nil
	}
	qty := normalizeTopLevelQuantity(item.Quantity, serverCount)
	unitPrice := parseOptionalFloat(item.UnitListPrice.FinancialAmount.MonetaryAmount)
	return &localdb.VendorSpecItem{
		SortOrder:        sortOrder,
		VendorPartnumber: code,
		Quantity:         qty,
		Description:      desc,
		UnitPrice:        unitPrice,
		TotalPrice:       totalPrice(unitPrice, qty),
	}
}

func vendorSpecItemFromSubLine(item cfxmlProductSubLineItem, sortOrder int) *localdb.VendorSpecItem {
	code := strings.TrimSpace(item.ProductIdentification.PartnerProductIdentification.ProprietaryProductIdentifier)
	desc := strings.TrimSpace(item.ProductIdentification.PartnerProductIdentification.ProductDescription)
	if code == "" && desc == "" {
		return nil
	}
	qty := maxInt(parseInt(item.Quantity), 1)
	unitPrice := parseOptionalFloat(item.UnitListPrice.FinancialAmount.MonetaryAmount)
	return &localdb.VendorSpecItem{
		SortOrder:        sortOrder,
		VendorPartnumber: code,
		Quantity:         qty,
		Description:      desc,
		UnitPrice:        unitPrice,
		TotalPrice:       totalPrice(unitPrice, qty),
	}
}

func sumVendorSpecRows(rows []localdb.VendorSpecItem, serverCount int) *float64 {
	total := 0.0
	hasTotal := false
	for _, row := range rows {
		if row.TotalPrice == nil {
			continue
		}
		total += *row.TotalPrice
		hasTotal = true
	}
	if !hasTotal {
		return nil
	}
	if serverCount > 1 {
		total *= float64(serverCount)
	}
	return &total
}

func totalPrice(unitPrice *float64, qty int) *float64 {
	if unitPrice == nil {
		return nil
	}
	total := *unitPrice * float64(qty)
	return &total
}

func parseOptionalFloat(raw string) *float64 {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil
	}
	value, err := strconv.ParseFloat(trimmed, 64)
	if err != nil {
		return nil
	}
	return &value
}

func parseInt(raw string) int {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return 0
	}
	value, err := strconv.Atoi(trimmed)
	if err != nil {
		return 0
	}
	return value
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}

func maxInt(value, floor int) int {
	if value < floor {
		return floor
	}
	return value
}

func normalizeTopLevelQuantity(raw string, serverCount int) int {
	qty := maxInt(parseInt(raw), 1)
	if serverCount <= 1 {
		return qty
	}
	if qty%serverCount == 0 {
		return maxInt(qty/serverCount, 1)
	}
	return qty
}

func IsCFXMLWorkspace(data []byte) bool {
	return bytes.Contains(data, []byte("<CFXML>")) || bytes.Contains(data, []byte("<CFXML "))
}
