package services

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

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

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

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

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

type TemplateFilter struct {
	Category string
	Search   string
	Limit    int
	Offset   int
}

type CreateTemplateInput struct {
	Name        string  `json:"name"`
	Description *string `json:"description,omitempty"`
	Category    string  `json:"category"`
	Content     string  `json:"content"`
	Variables   []byte  `json:"variables,omitempty"`
}

type UpdateTemplateInput struct {
	Name        *string `json:"name,omitempty"`
	Description *string `json:"description,omitempty"`
	Category    *string `json:"category,omitempty"`
	Content     *string `json:"content,omitempty"`
	Variables   []byte  `json:"variables,omitempty"`
}

var validCategories = map[string]bool{
	"schriftsatz":    true,
	"vertrag":        true,
	"korrespondenz":  true,
	"intern":         true,
}

func (s *TemplateService) List(ctx context.Context, tenantID uuid.UUID, filter TemplateFilter) ([]models.DocumentTemplate, int, error) {
	if filter.Limit <= 0 {
		filter.Limit = 50
	}
	if filter.Limit > 100 {
		filter.Limit = 100
	}

	// Show system templates + tenant's own templates
	where := "WHERE (tenant_id = $1 OR is_system = true)"
	args := []any{tenantID}
	argIdx := 2

	if filter.Category != "" {
		where += fmt.Sprintf(" AND category = $%d", argIdx)
		args = append(args, filter.Category)
		argIdx++
	}
	if filter.Search != "" {
		where += fmt.Sprintf(" AND (name ILIKE $%d OR description ILIKE $%d)", argIdx, argIdx)
		args = append(args, "%"+filter.Search+"%")
		argIdx++
	}

	var total int
	countQ := "SELECT COUNT(*) FROM document_templates " + where
	if err := s.db.GetContext(ctx, &total, countQ, args...); err != nil {
		return nil, 0, fmt.Errorf("counting templates: %w", err)
	}

	query := "SELECT * FROM document_templates " + where + " ORDER BY is_system DESC, name ASC"
	query += fmt.Sprintf(" LIMIT $%d OFFSET $%d", argIdx, argIdx+1)
	args = append(args, filter.Limit, filter.Offset)

	var templates []models.DocumentTemplate
	if err := s.db.SelectContext(ctx, &templates, query, args...); err != nil {
		return nil, 0, fmt.Errorf("listing templates: %w", err)
	}

	return templates, total, nil
}

func (s *TemplateService) GetByID(ctx context.Context, tenantID, templateID uuid.UUID) (*models.DocumentTemplate, error) {
	var t models.DocumentTemplate
	err := s.db.GetContext(ctx, &t,
		"SELECT * FROM document_templates WHERE id = $1 AND (tenant_id = $2 OR is_system = true)",
		templateID, tenantID)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("getting template: %w", err)
	}
	return &t, nil
}

func (s *TemplateService) Create(ctx context.Context, tenantID uuid.UUID, input CreateTemplateInput) (*models.DocumentTemplate, error) {
	if input.Name == "" {
		return nil, fmt.Errorf("name is required")
	}
	if !validCategories[input.Category] {
		return nil, fmt.Errorf("invalid category: %s", input.Category)
	}

	variables := input.Variables
	if variables == nil {
		variables = []byte("[]")
	}

	var t models.DocumentTemplate
	err := s.db.GetContext(ctx, &t,
		`INSERT INTO document_templates (tenant_id, name, description, category, content, variables, is_system)
		 VALUES ($1, $2, $3, $4, $5, $6, false)
		 RETURNING *`,
		tenantID, input.Name, input.Description, input.Category, input.Content, variables)
	if err != nil {
		return nil, fmt.Errorf("creating template: %w", err)
	}

	s.audit.Log(ctx, "create", "document_template", &t.ID, nil, t)
	return &t, nil
}

func (s *TemplateService) Update(ctx context.Context, tenantID, templateID uuid.UUID, input UpdateTemplateInput) (*models.DocumentTemplate, error) {
	// Don't allow editing system templates
	existing, err := s.GetByID(ctx, tenantID, templateID)
	if err != nil {
		return nil, err
	}
	if existing == nil {
		return nil, nil
	}
	if existing.IsSystem {
		return nil, fmt.Errorf("system templates cannot be edited")
	}
	if existing.TenantID == nil || *existing.TenantID != tenantID {
		return nil, fmt.Errorf("template does not belong to tenant")
	}

	sets := []string{}
	args := []any{}
	argIdx := 1

	if input.Name != nil {
		sets = append(sets, fmt.Sprintf("name = $%d", argIdx))
		args = append(args, *input.Name)
		argIdx++
	}
	if input.Description != nil {
		sets = append(sets, fmt.Sprintf("description = $%d", argIdx))
		args = append(args, *input.Description)
		argIdx++
	}
	if input.Category != nil {
		if !validCategories[*input.Category] {
			return nil, fmt.Errorf("invalid category: %s", *input.Category)
		}
		sets = append(sets, fmt.Sprintf("category = $%d", argIdx))
		args = append(args, *input.Category)
		argIdx++
	}
	if input.Content != nil {
		sets = append(sets, fmt.Sprintf("content = $%d", argIdx))
		args = append(args, *input.Content)
		argIdx++
	}
	if input.Variables != nil {
		sets = append(sets, fmt.Sprintf("variables = $%d", argIdx))
		args = append(args, input.Variables)
		argIdx++
	}

	if len(sets) == 0 {
		return existing, nil
	}

	sets = append(sets, "updated_at = now()")
	query := fmt.Sprintf("UPDATE document_templates SET %s WHERE id = $%d AND tenant_id = $%d RETURNING *",
		strings.Join(sets, ", "), argIdx, argIdx+1)
	args = append(args, templateID, tenantID)

	var t models.DocumentTemplate
	if err := s.db.GetContext(ctx, &t, query, args...); err != nil {
		return nil, fmt.Errorf("updating template: %w", err)
	}

	s.audit.Log(ctx, "update", "document_template", &t.ID, existing, t)
	return &t, nil
}

func (s *TemplateService) Delete(ctx context.Context, tenantID, templateID uuid.UUID) error {
	// Don't allow deleting system templates
	existing, err := s.GetByID(ctx, tenantID, templateID)
	if err != nil {
		return err
	}
	if existing == nil {
		return fmt.Errorf("template not found")
	}
	if existing.IsSystem {
		return fmt.Errorf("system templates cannot be deleted")
	}
	if existing.TenantID == nil || *existing.TenantID != tenantID {
		return fmt.Errorf("template does not belong to tenant")
	}

	_, err = s.db.ExecContext(ctx, "DELETE FROM document_templates WHERE id = $1 AND tenant_id = $2", templateID, tenantID)
	if err != nil {
		return fmt.Errorf("deleting template: %w", err)
	}

	s.audit.Log(ctx, "delete", "document_template", &templateID, existing, nil)
	return nil
}

// RenderData holds all the data available for template variable replacement.
type RenderData struct {
	Case     *models.Case
	Parties  []models.Party
	Tenant   *models.Tenant
	Deadline *models.Deadline
	UserName string
	UserEmail string
}

// Render replaces {{placeholders}} in the template content with actual data.
func (s *TemplateService) Render(template *models.DocumentTemplate, data RenderData) string {
	content := template.Content

	now := time.Now()

	replacements := map[string]string{
		"{{date.today}}":      now.Format("02.01.2006"),
		"{{date.today_long}}": formatGermanDate(now),
	}

	// Case data
	if data.Case != nil {
		replacements["{{case.number}}"] = data.Case.CaseNumber
		replacements["{{case.title}}"] = data.Case.Title
		if data.Case.Court != nil {
			replacements["{{case.court}}"] = *data.Case.Court
		}
		if data.Case.CourtRef != nil {
			replacements["{{case.court_ref}}"] = *data.Case.CourtRef
		}
	}

	// Party data
	for _, p := range data.Parties {
		role := ""
		if p.Role != nil {
			role = *p.Role
		}
		switch role {
		case "claimant", "plaintiff", "klaeger":
			replacements["{{party.claimant.name}}"] = p.Name
			if p.Representative != nil {
				replacements["{{party.claimant.representative}}"] = *p.Representative
			}
		case "defendant", "beklagter":
			replacements["{{party.defendant.name}}"] = p.Name
			if p.Representative != nil {
				replacements["{{party.defendant.representative}}"] = *p.Representative
			}
		}
	}

	// Tenant data
	if data.Tenant != nil {
		replacements["{{tenant.name}}"] = data.Tenant.Name
		// Extract address from settings if available
		replacements["{{tenant.address}}"] = extractSettingsField(data.Tenant.Settings, "address")
	}

	// User data
	replacements["{{user.name}}"] = data.UserName
	replacements["{{user.email}}"] = data.UserEmail

	// Deadline data
	if data.Deadline != nil {
		replacements["{{deadline.title}}"] = data.Deadline.Title
		replacements["{{deadline.due_date}}"] = data.Deadline.DueDate
	}

	for placeholder, value := range replacements {
		content = strings.ReplaceAll(content, placeholder, value)
	}

	return content
}

func formatGermanDate(t time.Time) string {
	months := []string{
		"Januar", "Februar", "März", "April", "Mai", "Juni",
		"Juli", "August", "September", "Oktober", "November", "Dezember",
	}
	return fmt.Sprintf("%d. %s %d", t.Day(), months[t.Month()-1], t.Year())
}

func extractSettingsField(settings []byte, field string) string {
	if len(settings) == 0 {
		return ""
	}
	var m map[string]any
	if err := json.Unmarshal(settings, &m); err != nil {
		return ""
	}
	if v, ok := m[field]; ok {
		if s, ok := v.(string); ok {
			return s
		}
	}
	return ""
}
