package services

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"

	"mgit.msbls.de/m/KanzlAI-mGMT/internal/models"
)

type InvoiceService struct {
	db    *sqlx.DB
	audit *AuditService
}

func NewInvoiceService(db *sqlx.DB, audit *AuditService) *InvoiceService {
	return &InvoiceService{db: db, audit: audit}
}

type CreateInvoiceInput struct {
	CaseID        uuid.UUID             `json:"case_id"`
	ClientName    string                `json:"client_name"`
	ClientAddress *string               `json:"client_address,omitempty"`
	Items         []models.InvoiceItem  `json:"items"`
	TaxRate       *float64              `json:"tax_rate,omitempty"`
	IssuedAt      *string               `json:"issued_at,omitempty"`
	DueAt         *string               `json:"due_at,omitempty"`
	Notes         *string               `json:"notes,omitempty"`
	TimeEntryIDs  []uuid.UUID           `json:"time_entry_ids,omitempty"`
}

type UpdateInvoiceInput struct {
	ClientName    *string               `json:"client_name,omitempty"`
	ClientAddress *string               `json:"client_address,omitempty"`
	Items         []models.InvoiceItem  `json:"items,omitempty"`
	TaxRate       *float64              `json:"tax_rate,omitempty"`
	IssuedAt      *string               `json:"issued_at,omitempty"`
	DueAt         *string               `json:"due_at,omitempty"`
	Notes         *string               `json:"notes,omitempty"`
}

const invoiceCols = `id, tenant_id, case_id, invoice_number, client_name, client_address,
	items, subtotal, tax_rate, tax_amount, total, status, issued_at, due_at, paid_at, notes,
	created_by, created_at, updated_at`

func (s *InvoiceService) List(ctx context.Context, tenantID uuid.UUID, caseID *uuid.UUID, status string) ([]models.Invoice, error) {
	where := "WHERE tenant_id = $1"
	args := []any{tenantID}
	argIdx := 2

	if caseID != nil {
		where += fmt.Sprintf(" AND case_id = $%d", argIdx)
		args = append(args, *caseID)
		argIdx++
	}
	if status != "" {
		where += fmt.Sprintf(" AND status = $%d", argIdx)
		args = append(args, status)
		argIdx++
	}

	var invoices []models.Invoice
	err := s.db.SelectContext(ctx, &invoices,
		fmt.Sprintf("SELECT %s FROM invoices %s ORDER BY created_at DESC", invoiceCols, where),
		args...)
	if err != nil {
		return nil, fmt.Errorf("list invoices: %w", err)
	}
	return invoices, nil
}

func (s *InvoiceService) GetByID(ctx context.Context, tenantID, invoiceID uuid.UUID) (*models.Invoice, error) {
	var inv models.Invoice
	err := s.db.GetContext(ctx, &inv,
		`SELECT `+invoiceCols+` FROM invoices WHERE tenant_id = $1 AND id = $2`,
		tenantID, invoiceID)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get invoice: %w", err)
	}
	return &inv, nil
}

func (s *InvoiceService) Create(ctx context.Context, tenantID, userID uuid.UUID, input CreateInvoiceInput) (*models.Invoice, error) {
	tx, err := s.db.BeginTxx(ctx, nil)
	if err != nil {
		return nil, fmt.Errorf("begin tx: %w", err)
	}
	defer tx.Rollback()

	// Generate invoice number: RE-YYYY-NNN
	year := time.Now().Year()
	var seq int
	err = tx.GetContext(ctx, &seq,
		`SELECT COUNT(*) + 1 FROM invoices WHERE tenant_id = $1 AND invoice_number LIKE $2`,
		tenantID, fmt.Sprintf("RE-%d-%%", year))
	if err != nil {
		return nil, fmt.Errorf("generate invoice number: %w", err)
	}
	invoiceNumber := fmt.Sprintf("RE-%d-%03d", year, seq)

	// Calculate totals
	taxRate := 19.00
	if input.TaxRate != nil {
		taxRate = *input.TaxRate
	}

	var subtotal float64
	for _, item := range input.Items {
		subtotal += item.Amount
	}
	taxAmount := subtotal * taxRate / 100
	total := subtotal + taxAmount

	itemsJSON, err := json.Marshal(input.Items)
	if err != nil {
		return nil, fmt.Errorf("marshal items: %w", err)
	}

	var inv models.Invoice
	err = tx.QueryRowxContext(ctx,
		`INSERT INTO invoices (tenant_id, case_id, invoice_number, client_name, client_address,
			items, subtotal, tax_rate, tax_amount, total, issued_at, due_at, notes, created_by)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
		 RETURNING `+invoiceCols,
		tenantID, input.CaseID, invoiceNumber, input.ClientName, input.ClientAddress,
		itemsJSON, subtotal, taxRate, taxAmount, total, input.IssuedAt, input.DueAt, input.Notes, userID,
	).StructScan(&inv)
	if err != nil {
		return nil, fmt.Errorf("create invoice: %w", err)
	}

	// Mark linked time entries as billed
	if len(input.TimeEntryIDs) > 0 {
		query, args, err := sqlx.In(
			`UPDATE time_entries SET billed = true, invoice_id = ? WHERE tenant_id = ? AND id IN (?)`,
			inv.ID, tenantID, input.TimeEntryIDs)
		if err != nil {
			return nil, fmt.Errorf("build time entry update: %w", err)
		}
		query = tx.Rebind(query)
		_, err = tx.ExecContext(ctx, query, args...)
		if err != nil {
			return nil, fmt.Errorf("mark time entries billed: %w", err)
		}
	}

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

	s.audit.Log(ctx, "create", "invoice", &inv.ID, nil, inv)
	return &inv, nil
}

func (s *InvoiceService) Update(ctx context.Context, tenantID, invoiceID uuid.UUID, input UpdateInvoiceInput) (*models.Invoice, error) {
	old, err := s.GetByID(ctx, tenantID, invoiceID)
	if err != nil {
		return nil, err
	}
	if old == nil {
		return nil, fmt.Errorf("invoice not found")
	}
	if old.Status != "draft" {
		return nil, fmt.Errorf("can only update draft invoices")
	}

	// Recalculate totals if items changed
	var itemsJSON json.RawMessage
	var subtotal float64
	taxRate := old.TaxRate

	if input.Items != nil {
		for _, item := range input.Items {
			subtotal += item.Amount
		}
		itemsJSON, _ = json.Marshal(input.Items)
	}
	if input.TaxRate != nil {
		taxRate = *input.TaxRate
	}

	if input.Items != nil {
		taxAmount := subtotal * taxRate / 100
		total := subtotal + taxAmount

		var inv models.Invoice
		err = s.db.QueryRowxContext(ctx,
			`UPDATE invoices SET
				client_name = COALESCE($3, client_name),
				client_address = COALESCE($4, client_address),
				items = $5,
				subtotal = $6,
				tax_rate = $7,
				tax_amount = $8,
				total = $9,
				issued_at = COALESCE($10, issued_at),
				due_at = COALESCE($11, due_at),
				notes = COALESCE($12, notes),
				updated_at = now()
			 WHERE tenant_id = $1 AND id = $2
			 RETURNING `+invoiceCols,
			tenantID, invoiceID, input.ClientName, input.ClientAddress,
			itemsJSON, subtotal, taxRate, subtotal*taxRate/100, total,
			input.IssuedAt, input.DueAt, input.Notes,
		).StructScan(&inv)
		if err != nil {
			return nil, fmt.Errorf("update invoice: %w", err)
		}
		s.audit.Log(ctx, "update", "invoice", &inv.ID, old, inv)
		return &inv, nil
	}

	// Update without changing items
	var inv models.Invoice
	err = s.db.QueryRowxContext(ctx,
		`UPDATE invoices SET
			client_name = COALESCE($3, client_name),
			client_address = COALESCE($4, client_address),
			tax_rate = COALESCE($5, tax_rate),
			issued_at = COALESCE($6, issued_at),
			due_at = COALESCE($7, due_at),
			notes = COALESCE($8, notes),
			updated_at = now()
		 WHERE tenant_id = $1 AND id = $2
		 RETURNING `+invoiceCols,
		tenantID, invoiceID, input.ClientName, input.ClientAddress,
		input.TaxRate, input.IssuedAt, input.DueAt, input.Notes,
	).StructScan(&inv)
	if err != nil {
		return nil, fmt.Errorf("update invoice: %w", err)
	}

	s.audit.Log(ctx, "update", "invoice", &inv.ID, old, inv)
	return &inv, nil
}

func (s *InvoiceService) UpdateStatus(ctx context.Context, tenantID, invoiceID uuid.UUID, newStatus string) (*models.Invoice, error) {
	old, err := s.GetByID(ctx, tenantID, invoiceID)
	if err != nil {
		return nil, err
	}
	if old == nil {
		return nil, fmt.Errorf("invoice not found")
	}

	// Validate transitions
	validTransitions := map[string][]string{
		"draft":     {"sent", "cancelled"},
		"sent":      {"paid", "cancelled"},
		"paid":      {},
		"cancelled": {},
	}
	allowed := validTransitions[old.Status]
	valid := false
	for _, s := range allowed {
		if s == newStatus {
			valid = true
			break
		}
	}
	if !valid {
		return nil, fmt.Errorf("invalid status transition from %s to %s", old.Status, newStatus)
	}

	var paidAt *time.Time
	if newStatus == "paid" {
		now := time.Now()
		paidAt = &now
	}

	var inv models.Invoice
	err = s.db.QueryRowxContext(ctx,
		`UPDATE invoices SET status = $3, paid_at = COALESCE($4, paid_at), updated_at = now()
		 WHERE tenant_id = $1 AND id = $2
		 RETURNING `+invoiceCols,
		tenantID, invoiceID, newStatus, paidAt,
	).StructScan(&inv)
	if err != nil {
		return nil, fmt.Errorf("update invoice status: %w", err)
	}

	s.audit.Log(ctx, "update", "invoice", &inv.ID, old, inv)
	return &inv, nil
}
