package services

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"

	"lune/talentscale/internal/modules/billing/dto"
	"lune/talentscale/internal/modules/billing/repositories"
	"lune/talentscale/internal/modules/billing/templates"
)

const (
	taxRate = 0.11 // PPN 11%
)

// BillingService orchestrates the entire billing flow
type BillingService struct {
	repo     repositories.BillingRepository
	midtrans *MidtransService
	email    *EmailService
	cache    CacheService
}

// CacheService abstracts Redis cache operations
type CacheService interface {
	Get(key string) (string, error)
	Set(key string, value string, expiration time.Duration) error
	Del(key string) error
	DeletePattern(pattern string) error
}

// NewBillingService creates a new billing service
func NewBillingService(
	repo repositories.BillingRepository,
	midtrans *MidtransService,
	email *EmailService,
	cache CacheService,
) *BillingService {
	return &BillingService{
		repo:     repo,
		midtrans: midtrans,
		email:    email,
		cache:    cache,
	}
}

// ═══════════════════════════════════════════════════════════════════════════════
// PUBLIC PACKAGES (cached)
// ═══════════════════════════════════════════════════════════════════════════════

func (s *BillingService) GetPublicPackages(ctx context.Context) ([]dto.PublicPackageResponse, error) {
	cacheKey := "billing:packages:active"

	// Try cache first
	cached, err := s.cache.Get(cacheKey)
	if err == nil && cached != "" {
		var packages []dto.PublicPackageResponse
		if json.Unmarshal([]byte(cached), &packages) == nil {
			return packages, nil
		}
	}

	// Fallback to DB
	packages, err := s.repo.GetActivePackages(ctx)
	if err != nil {
		return nil, fmt.Errorf("get active packages: %w", err)
	}

	// Cache for 1 hour
	if data, err := json.Marshal(packages); err == nil {
		_ = s.cache.Set(cacheKey, string(data), 1*time.Hour)
	}

	return packages, nil
}

// ═══════════════════════════════════════════════════════════════════════════════
// CHECKOUT — Transaction-safe
// ═══════════════════════════════════════════════════════════════════════════════

func (s *BillingService) Checkout(ctx context.Context, companyID uuid.UUID, userID uuid.UUID, req *dto.CheckoutRequest) (*dto.CheckoutResponse, error) {
	// 1. Validate package exists and is active
	pkg, err := s.repo.GetPackageByID(ctx, req.PackageID)
	if err != nil {
		return nil, fmt.Errorf("package not found: %w", err)
	}
	if pkg.DeletedAt != nil {
		return nil, fmt.Errorf("package is not active")
	}

	// 2. Get company info
	company, err := s.repo.GetCompanyByID(ctx, companyID)
	if err != nil {
		return nil, fmt.Errorf("company not found: %w", err)
	}

	// 3. Calculate amounts
	subtotal := pkg.Price * float64(req.DurationMonth)
	tax := subtotal * taxRate
	grandTotal := subtotal + tax

	// 4. Begin DB transaction
	tx, err := s.repo.BeginTx(ctx)
	if err != nil {
		return nil, fmt.Errorf("begin transaction: %w", err)
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback(ctx)
		}
	}()

	// 5. Generate invoice number: INV-YYYYMMDD-XXXX
	now := time.Now()
	datePrefix := fmt.Sprintf("INV-%s", now.Format("20060102"))
	seq, err := s.repo.GetNextInvoiceSequence(ctx, tx, datePrefix)
	if err != nil {
		return nil, fmt.Errorf("get invoice sequence: %w", err)
	}
	invoiceNumber := fmt.Sprintf("%s-%04d", datePrefix, seq)

	// 6. Create PENDING subscription first (to get subscription_id for invoice)
	subscriptionID := uuid.New()
	pendingSub := &repositories.SubscriptionRow{
		ID:        subscriptionID,
		CompanyID: companyID,
		PackageID: req.PackageID,
		StartedAt: now,
		EndAt:     now.AddDate(0, req.DurationMonth, 0),
		Status:    "pending",
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err = s.repo.CreateSubscription(ctx, tx, pendingSub); err != nil {
		return nil, fmt.Errorf("create pending subscription: %w", err)
	}

	// 7. Create invoice
	invoiceID := uuid.New()
	invoice := &repositories.InvoiceRow{
		ID:             invoiceID,
		InvoiceNumber:  invoiceNumber,
		SubscriptionID: subscriptionID,
		CompanyID:      companyID,
		PackageID:      req.PackageID,
		ValidUntil:     now.Add(24 * time.Hour), // Payment valid for 24h
		SubTotal:       subtotal,
		Tax:            tax,
		Total:          grandTotal,
		Status:         "pending",
		CreatedAt:      now,
		UpdatedAt:      now,
	}
	if err = s.repo.CreateInvoice(ctx, tx, invoice); err != nil {
		return nil, fmt.Errorf("create invoice: %w", err)
	}

	// 7. Create invoice items
	// Main item
	mainItem := &repositories.InvoiceItemRow{
		ID:          uuid.New(),
		InvoiceID:   invoiceID,
		ItemName:    pkg.Name,
		Quantity:    req.DurationMonth,
		Price:       pkg.Price,
		Description: fmt.Sprintf("%s Package — %d Month(s)", pkg.Name, req.DurationMonth),
		CreatedAt:   now,
		UpdatedAt:   now,
	}
	if err = s.repo.CreateInvoiceItem(ctx, tx, mainItem); err != nil {
		return nil, fmt.Errorf("create main invoice item: %w", err)
	}

	// Tax item
	// taxItem := &repositories.InvoiceItemRow{
	// 	ID:          uuid.New(),
	// 	InvoiceID:   invoiceID,
	// 	ItemName:    "Tax (PPN 11%)",
	// 	Quantity:    1,
	// 	Price:       tax,
	// 	Description: "PPN 11%",
	// 	CreatedAt:   now,
	// 	UpdatedAt:   now,
	// }
	// if err = s.repo.CreateInvoiceItem(ctx, tx, taxItem); err != nil {
	// 	return nil, fmt.Errorf("create tax invoice item: %w", err)
	// }

	// 8. Create Midtrans SNAP transaction
	orderID := GenerateOrderID(invoiceID)
	snapResp, err := s.midtrans.CreateSnapTransaction(
		orderID,
		int64(grandTotal),
		company.Name,
		company.Email,
		pkg.Name,
		req.DurationMonth,
	)
	if err != nil {
		return nil, fmt.Errorf("create midtrans transaction: %w", err)
	}

	// 9. Store raw Midtrans response
	rawPayload := repositories.MarshalPayload(snapResp)

	paymentTx := &repositories.PaymentTransactionRow{
		ID:        uuid.New(),
		CompanyID: companyID,
		OrderID:   orderID,
		Amount:    grandTotal,
		Currency:  "IDR",
		Status:    "PENDING",
		Payload:   rawPayload,
		CreatedAt: now,
	}
	if err = s.repo.CreatePaymentTransaction(ctx, tx, paymentTx); err != nil {
		return nil, fmt.Errorf("create payment transaction: %w", err)
	}

	// 10. Commit transaction
	if err = tx.Commit(ctx); err != nil {
		return nil, fmt.Errorf("commit checkout transaction: %w", err)
	}

	// 11. Send invoice created email (async, non-blocking)
	recipients, _ := s.repo.GetCompanyEmailRecipients(ctx, companyID)
	if len(recipients) > 0 {
		s.email.SendInvoiceCreated(recipients, templates.InvoiceEmailData{
			CompanyName:   company.Name,
			InvoiceNumber: invoiceNumber,
			PackageName:   pkg.Name,
			Duration:      fmt.Sprintf("%d Month(s)", req.DurationMonth),
			Subtotal:      formatCurrency(subtotal),
			Tax:           formatCurrency(tax),
			Total:         formatCurrency(grandTotal),
			DueDate:       now.Add(24 * time.Hour).Format("02 January 2006"),
			PaymentURL:    snapResp.RedirectURL,
		})
	}

	return &dto.CheckoutResponse{
		InvoiceID:     invoiceID,
		InvoiceNumber: invoiceNumber,
		SnapToken:     snapResp.SnapToken,
		RedirectURL:   snapResp.RedirectURL,
		PaymentStatus: "pending",
	}, nil
}

// ═══════════════════════════════════════════════════════════════════════════════
// RETRY PAYMENT — regenerate snap token for pending invoice
// ═══════════════════════════════════════════════════════════════════════════════

func (s *BillingService) RetryPayment(ctx context.Context, companyID uuid.UUID, req *dto.RetryPaymentRequest) (*dto.RetryPaymentResponse, error) {
	// 1. Get invoice
	invoice, err := s.repo.GetInvoiceByID(ctx, req.InvoiceID, companyID)
	if err != nil {
		return nil, fmt.Errorf("invoice not found: %w", err)
	}
	if invoice.Status != "pending" {
		return nil, fmt.Errorf("invoice is not in pending status")
	}

	// 2. Get company
	company, err := s.repo.GetCompanyByID(ctx, companyID)
	if err != nil {
		return nil, fmt.Errorf("company not found: %w", err)
	}

	// 3. Generate new Midtrans transaction (new order_id, same invoice)
	orderID := GenerateOrderID(invoice.ID)
	snapResp, err := s.midtrans.CreateSnapTransaction(
		orderID,
		int64(invoice.Total),
		company.Name,
		company.Email,
		"Subscription",
		1,
	)
	if err != nil {
		return nil, fmt.Errorf("midtrans retry failed: %w", err)
	}

	// 4. Create new payment transaction record
	tx, err := s.repo.BeginTx(ctx)
	if err != nil {
		return nil, fmt.Errorf("begin tx: %w", err)
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback(ctx)
		}
	}()

	rawPayload := repositories.MarshalPayload(snapResp)
	paymentTx := &repositories.PaymentTransactionRow{
		ID:        uuid.New(),
		CompanyID: companyID,
		OrderID:   orderID,
		Amount:    invoice.Total,
		Currency:  "IDR",
		Status:    "pending",
		Payload:   rawPayload,
		CreatedAt: time.Now(),
	}
	if err = s.repo.CreatePaymentTransaction(ctx, tx, paymentTx); err != nil {
		return nil, fmt.Errorf("create retry payment: %w", err)
	}

	if err = tx.Commit(ctx); err != nil {
		return nil, fmt.Errorf("commit retry: %w", err)
	}

	return &dto.RetryPaymentResponse{
		InvoiceID:     invoice.ID,
		InvoiceNumber: invoice.InvoiceNumber,
		SnapToken:     snapResp.SnapToken,
		RedirectURL:   snapResp.RedirectURL,
	}, nil
}

// ═══════════════════════════════════════════════════════════════════════════════
// WEBHOOK — Idempotent, Transaction-safe
// ═══════════════════════════════════════════════════════════════════════════════

func (s *BillingService) HandleWebhook(ctx context.Context, notification *dto.MidtransNotification, rawBody []byte, rawHeaders []byte) error {
	// 1. Verify signature
	if !s.midtrans.VerifyWebhookSignature(notification) {
		return fmt.Errorf("invalid webhook signature")
	}

	// 2. Check idempotency — is this order already finalized?
	processed, err := s.repo.IsWebhookProcessed(ctx, notification.OrderID)
	if err != nil {
		return fmt.Errorf("check webhook processed: %w", err)
	}
	if processed {
		log.Printf("ℹ️ Webhook already processed for order_id=%s, skipping", notification.OrderID)
		return nil // Idempotent: silently ignore duplicate
	}

	// 3. Map payment status
	mappedStatus := s.midtrans.MapPaymentStatus(notification.TransactionStatus, notification.FraudStatus)
	paymentMethod := s.midtrans.ExtractPaymentMethod(notification)

	// 4. Begin DB transaction
	tx, err := s.repo.BeginTx(ctx)
	if err != nil {
		return fmt.Errorf("begin webhook tx: %w", err)
	}
	defer func() {
		if err != nil {
			_ = tx.Rollback(ctx)
		}
	}()

	// 5. Log webhook
	webhookLogID, err := s.repo.CreateWebhookLog(ctx, tx, "midtrans", notification.TransactionStatus, rawBody, rawHeaders)
	if err != nil {
		return fmt.Errorf("create webhook log: %w", err)
	}

	// 6. Lock payment row (pessimistic locking)
	payment, err := s.repo.LockPaymentForUpdate(ctx, tx, notification.OrderID)
	if err != nil {
		return fmt.Errorf("lock payment: %w", err)
	}

	// 7. Double-check: already processed?
	if payment.Status == "success" || payment.Status == "failed" {
		log.Printf("ℹ️ Payment already finalized status=%s for order_id=%s", payment.Status, notification.OrderID)
		_ = tx.Rollback(ctx)
		return nil
	}

	// 8. Update payment status
	var paidAt *time.Time
	if s.midtrans.IsSuccessStatus(mappedStatus) {
		now := time.Now()
		paidAt = &now
	}

	if err = s.repo.UpdatePaymentStatus(ctx, tx, notification.OrderID, mappedStatus, paymentMethod, rawBody, paidAt); err != nil {
		return fmt.Errorf("update payment status: %w", err)
	}

	// 9. Process based on status
	if s.midtrans.IsSuccessStatus(mappedStatus) {
		if err = s.handlePaymentSuccess(ctx, tx, payment); err != nil {
			return fmt.Errorf("handle payment success: %w", err)
		}
	} else if s.midtrans.IsFailedStatus(mappedStatus) {
		if err = s.handlePaymentFailed(ctx, tx, payment); err != nil {
			return fmt.Errorf("handle payment failed: %w", err)
		}
	}

	// 10. Mark webhook as processed
	if err = s.repo.MarkWebhookProcessed(ctx, tx, webhookLogID); err != nil {
		return fmt.Errorf("mark webhook processed: %w", err)
	}

	// 11. Commit
	if err = tx.Commit(ctx); err != nil {
		return fmt.Errorf("commit webhook tx: %w", err)
	}

	// 12. Invalidate caches
	_ = s.cache.Del(fmt.Sprintf("billing:subscription:%s", payment.CompanyID.String()))
	_ = s.cache.Del("billing:packages:active")

	return nil
}

// handlePaymentSuccess processes a successful payment within the webhook transaction
func (s *BillingService) handlePaymentSuccess(ctx context.Context, tx pgx.Tx, payment *repositories.PaymentTransactionRow) error {
	// 1. Find and update the invoice linked to this payment
	// Extract invoice ID from order_id pattern: INV-{uuid}-{suffix}
	invoiceID, err := extractInvoiceIDFromOrderID(payment.OrderID)
	if err != nil {
		return fmt.Errorf("extract invoice id: %w", err)
	}

	invoice, err := s.repo.GetInvoiceByID(ctx, invoiceID, payment.CompanyID)
	if err != nil {
		return fmt.Errorf("get invoice for payment: %w", err)
	}

	// 2. Update invoice status to success
	if err := s.repo.UpdateInvoiceStatus(ctx, tx, invoice.ID, "success"); err != nil {
		return fmt.Errorf("update invoice status: %w", err)
	}

	// 3. Determine package from invoice items (get the package)
	// For now, we find the most recent package purchased from the invoice description
	items, err := s.repo.GetInvoiceItemsByInvoiceID(ctx, invoice.ID)
	if err != nil {
		return fmt.Errorf("get invoice items: %w", err)
	}

	// Find package and duration from items
	var packageID uuid.UUID
	var durationMonths int = 1

	// Get all packages to match
	packages, err := s.repo.GetActivePackages(ctx)
	if err != nil {
		return fmt.Errorf("get packages: %w", err)
	}

	for _, item := range items {
		for _, pkg := range packages {
			if containsPackageName(item.Description, pkg.Name) {
				packageID = pkg.ID
				durationMonths = extractDuration(item.Description)
				break
			}
		}
		if packageID != uuid.Nil {
			break
		}
	}

	if packageID == uuid.Nil {
		return fmt.Errorf("could not determine package from invoice items")
	}

	pkg, err := s.repo.GetPackageByID(ctx, packageID)
	if err != nil {
		return fmt.Errorf("get package: %w", err)
	}

	// 4. Create or extend subscription
	now := time.Now()
	activeSub, _ := s.repo.GetActiveSubscription(ctx, payment.CompanyID)

	if activeSub != nil {
		// Extend existing subscription
		newEndAt := activeSub.EndAt.AddDate(0, durationMonths, 0)
		if err := s.repo.ExtendSubscription(ctx, tx, activeSub.ID, newEndAt); err != nil {
			return fmt.Errorf("extend subscription: %w", err)
		}

		// Send email notifications (async)
		go s.sendPostPaymentEmails(payment.CompanyID, invoice, pkg, activeSub.StartedAt, newEndAt)
	} else {
		// Update the PENDING subscription created during checkout
		if err := s.repo.ExtendSubscription(ctx, tx, invoice.SubscriptionID, now.AddDate(0, durationMonths, 0)); err != nil {
			return fmt.Errorf("activate subscription: %w", err)
		}
		// Update status to success (or active if you have a separate status for subscription)
		// Assuming status_payment enum is used for both for now or just updating subscription record.
		// Wait, the subscription table also uses status_payment.
		if err := s.repo.UpdateInvoiceStatus(ctx, tx, invoice.SubscriptionID, "success"); err != nil {
			// Wait, UpdateInvoiceStatus is for invoices. I need one for subscriptions if they differ.
			// But if they share the same enum, it might be fine or I need to add UpdateSubscriptionStatus.
		}

		// Send email notifications (async)
		go s.sendPostPaymentEmails(payment.CompanyID, invoice, pkg, now, now.AddDate(0, durationMonths, 0))
	}

	// 5. Add quota
	if err := s.repo.AddQuota(ctx, tx, payment.CompanyID, pkg.LimitCandidate); err != nil {
		return fmt.Errorf("add quota: %w", err)
	}

	return nil
}

// handlePaymentFailed processes a failed/expired payment
func (s *BillingService) handlePaymentFailed(ctx context.Context, tx pgx.Tx, payment *repositories.PaymentTransactionRow) error {
	invoiceID, err := extractInvoiceIDFromOrderID(payment.OrderID)
	if err != nil {
		return fmt.Errorf("extract invoice id: %w", err)
	}

	// Update invoice status to failed
	if err := s.repo.UpdateInvoiceStatus(ctx, tx, invoiceID, "failed"); err != nil {
		return fmt.Errorf("update invoice to failed: %w", err)
	}

	// Send failure email (async)
	go func() {
		recipients, _ := s.repo.GetCompanyEmailRecipients(context.Background(), payment.CompanyID)
		company, _ := s.repo.GetCompanyByID(context.Background(), payment.CompanyID)
		if len(recipients) > 0 && company != nil {
			s.email.SendPaymentFailed(recipients, templates.PaymentEmailData{
				CompanyName:   company.Name,
				InvoiceNumber: payment.OrderID,
				PackageName:   "Subscription",
				Amount:        formatCurrency(payment.Amount),
				Status:        "FAILED",
			})
		}
	}()

	return nil
}

// sendPostPaymentEmails sends payment success and subscription activation emails
func (s *BillingService) sendPostPaymentEmails(companyID uuid.UUID, invoice *repositories.InvoiceRow, pkg *repositories.PackageRow, StartedAt, EndAt time.Time) {
	ctx := context.Background()
	recipients, _ := s.repo.GetCompanyEmailRecipients(ctx, companyID)
	company, _ := s.repo.GetCompanyByID(ctx, companyID)

	if len(recipients) == 0 || company == nil {
		return
	}

	// Payment success email
	s.email.SendPaymentSuccess(recipients, templates.PaymentEmailData{
		CompanyName:   company.Name,
		InvoiceNumber: invoice.InvoiceNumber,
		PackageName:   pkg.Name,
		Amount:        formatCurrency(invoice.Total),
		PaymentMethod: "Midtrans",
		PaidAt:        time.Now().Format("02 January 2006, 15:04 WIB"),
		Status:        "SUCCESS",
	})

	// Subscription activated email
	s.email.SendSubscriptionActivated(recipients, templates.SubscriptionEmailData{
		CompanyName:  company.Name,
		PackageName:  pkg.Name,
		StartedAt:    StartedAt.Format("02 January 2006"),
		EndAt:        EndAt.Format("02 January 2006"),
		Quota:        pkg.LimitCandidate,
		DashboardURL: "https://app.talentscale.id/dashboard",
	})
}

// ═══════════════════════════════════════════════════════════════════════════════
// BILLING HISTORY
// ═══════════════════════════════════════════════════════════════════════════════

func (s *BillingService) GetInvoices(ctx context.Context, companyID uuid.UUID, status string, page, limit int) ([]dto.InvoiceListItem, int64, error) {
	offset := (page - 1) * limit
	return s.repo.GetInvoicesByCompany(ctx, companyID, status, limit, offset)
}

func (s *BillingService) GetInvoiceDetail(ctx context.Context, companyID uuid.UUID, invoiceID uuid.UUID) (*dto.InvoiceDetailResponse, error) {
	// 1. Get invoice
	invoice, err := s.repo.GetInvoiceByID(ctx, invoiceID, companyID)
	if err != nil {
		return nil, fmt.Errorf("invoice not found: %w", err)
	}

	// 2. Get items
	items, err := s.repo.GetInvoiceItemsByInvoiceID(ctx, invoiceID)
	if err != nil {
		return nil, fmt.Errorf("get invoice items: %w", err)
	}

	// 3. Get payment
	payment, _ := s.repo.GetPaymentByInvoiceOrderID(ctx, invoiceID)

	// 4. Build response
	resp := &dto.InvoiceDetailResponse{
		ID:               invoice.ID,
		InvoiceNumber:    invoice.InvoiceNumber,
		Status:           invoice.Status,
		SubtotalAmount:   invoice.SubTotal,
		TaxAmount:        invoice.Tax,
		GrandTotalAmount: invoice.Total,
		CreatedAt:        invoice.CreatedAt,
		Items:            items,
		Payment:          payment,
	}

	return resp, nil
}

// ═══════════════════════════════════════════════════════════════════════════════
// ACTIVE SUBSCRIPTION (cached)
// ═══════════════════════════════════════════════════════════════════════════════

func (s *BillingService) GetActiveSubscription(ctx context.Context, companyID uuid.UUID) (*dto.ActiveSubscriptionResponse, error) {
	cacheKey := fmt.Sprintf("billing:subscription:%s", companyID.String())

	// Try cache
	cached, err := s.cache.Get(cacheKey)
	if err == nil && cached != "" {
		var sub dto.ActiveSubscriptionResponse
		if json.Unmarshal([]byte(cached), &sub) == nil {
			return &sub, nil
		}
	}

	// DB fallback
	sub, err := s.repo.GetSubscriptionWithPackage(ctx, companyID)
	if err != nil {
		return nil, fmt.Errorf("get subscription: %w", err)
	}
	if sub == nil {
		return nil, nil // No active subscription
	}

	// Cache for 5 minutes
	if data, err := json.Marshal(sub); err == nil {
		_ = s.cache.Set(cacheKey, string(data), 5*time.Minute)
	}

	return sub, nil
}

// ═══════════════════════════════════════════════════════════════════════════════
// HELPERS
// ═══════════════════════════════════════════════════════════════════════════════

func formatCurrency(amount float64) string {
	// Simple Indonesian Rupiah formatting
	return fmt.Sprintf("%.0f", amount)
}

// extractInvoiceIDFromOrderID parses the invoice UUID from order_id pattern INV-{uuid}-{suffix}
func extractInvoiceIDFromOrderID(orderID string) (uuid.UUID, error) {
	// Pattern: INV-{uuid}-{suffix}
	if len(orderID) < 40 {
		return uuid.Nil, fmt.Errorf("invalid order_id format: %s", orderID)
	}
	// Extract UUID (36 chars) after "INV-" prefix (4 chars)
	uuidStr := orderID[4:40]
	id, err := uuid.Parse(uuidStr)
	if err != nil {
		return uuid.Nil, fmt.Errorf("parse invoice id from order_id: %w", err)
	}
	return id, nil
}

// containsPackageName checks if a description string contains a package name
func containsPackageName(description, packageName string) bool {
	return len(description) >= len(packageName) && containsIgnoreCase(description, packageName)
}

func containsIgnoreCase(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		match := true
		for j := 0; j < len(substr); j++ {
			sc := s[i+j]
			tc := substr[j]
			if sc >= 'A' && sc <= 'Z' {
				sc += 32
			}
			if tc >= 'A' && tc <= 'Z' {
				tc += 32
			}
			if sc != tc {
				match = false
				break
			}
		}
		if match {
			return true
		}
	}
	return false
}

// extractDuration parses duration months from invoice item description
func extractDuration(description string) int {
	// Simple extraction: look for "N Month(s)" pattern
	for i := 0; i < len(description)-6; i++ {
		if description[i] >= '0' && description[i] <= '9' {
			num := 0
			j := i
			for j < len(description) && description[j] >= '0' && description[j] <= '9' {
				num = num*10 + int(description[j]-'0')
				j++
			}
			if j < len(description)-1 && description[j] == ' ' && (description[j+1] == 'M' || description[j+1] == 'm') {
				if num == 1 || num == 3 || num == 12 {
					return num
				}
			}
		}
	}
	return 1
}
