package packs

import (
	"context"
	"crypto/sha1"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
)

type sourceBackedEmailTemplateDefinition struct {
	Name          string  `json:"name,omitempty"`
	DeveloperName string  `json:"developerName,omitempty"`
	Subject       string  `json:"subject"`
	HTMLValue     string  `json:"htmlValue,omitempty"`
	Body          string  `json:"body,omitempty"`
	FolderID      *string `json:"folderId,omitempty"`
	Description   *string `json:"description,omitempty"`
	TemplateType  string  `json:"templateType,omitempty"`
	IsActive      *bool   `json:"isActive,omitempty"`
}

type sourceBackedEmailTemplateProjection struct {
	ID            string  `json:"id"`
	OwnerPack     string  `json:"ownerPack"`
	Name          string  `json:"name"`
	DeveloperName string  `json:"developerName"`
	Subject       string  `json:"subject"`
	HTMLValue     string  `json:"htmlValue,omitempty"`
	Body          string  `json:"body,omitempty"`
	FolderID      *string `json:"folderId,omitempty"`
	Description   *string `json:"description,omitempty"`
	TemplateType  string  `json:"templateType"`
	IsActive      bool    `json:"isActive"`
}

type sourceBackedEmailTemplateComponent struct {
	packName          string
	manifestComponent ManifestComponent
	projection        sourceBackedEmailTemplateProjection
	payload           []byte
}

func newSourceBackedEmailTemplateComponent(packName string, component ManifestComponent, payload []byte) (*sourceBackedEmailTemplateComponent, error) {
	_, projection, err := sourceBackedEmailTemplateProjectionFromPayload(packName, component, payload)
	if err != nil {
		return nil, err
	}
	return &sourceBackedEmailTemplateComponent{
		packName:          strings.TrimSpace(packName),
		manifestComponent: component,
		projection:        projection,
		payload:           append([]byte(nil), payload...),
	}, nil
}

func (c *sourceBackedEmailTemplateComponent) Kind() ComponentKind {
	return ComponentKindEmailTemplate
}

func (c *sourceBackedEmailTemplateComponent) PackName() string {
	return c.packName
}

func (c *sourceBackedEmailTemplateComponent) BusinessKey() string {
	return strings.TrimSpace(c.manifestComponent.BusinessKey)
}

func (c *sourceBackedEmailTemplateComponent) Manageability() Manageability {
	return c.manifestComponent.Manageability
}

func (c *sourceBackedEmailTemplateComponent) ExpectedPayload() ([]byte, error) {
	return append([]byte(nil), c.payload...), nil
}

func (c *sourceBackedEmailTemplateComponent) ExpectedProjection() (any, error) {
	return clonePackComponentProjection(c.projection)
}

func (c *sourceBackedEmailTemplateComponent) LiveProjection(ctx context.Context, deps VerifyDeps) (any, error) {
	return liveSourceBackedEmailTemplateProjection(ctx, deps.DB, c.packName, c.projection.DeveloperName)
}

func (c *sourceBackedEmailTemplateComponent) Apply(ctx context.Context, deps ApplyDeps) error {
	return upsertSourceBackedEmailTemplate(ctx, deps.DB, c.projection)
}

func sourceBackedEmailTemplateProjectionFromPayload(packName string, component ManifestComponent, payload []byte) (sourceBackedEmailTemplateDefinition, sourceBackedEmailTemplateProjection, error) {
	packName = strings.TrimSpace(packName)
	if packName == "" {
		return sourceBackedEmailTemplateDefinition{}, sourceBackedEmailTemplateProjection{}, fmt.Errorf("email_template component: pack name is required")
	}
	var def sourceBackedEmailTemplateDefinition
	if err := json.Unmarshal(payload, &def); err != nil {
		return sourceBackedEmailTemplateDefinition{}, sourceBackedEmailTemplateProjection{}, fmt.Errorf("parse %s: %w", componentPayloadLabel(component), err)
	}
	def.Name = strings.TrimSpace(def.Name)
	def.DeveloperName = strings.TrimSpace(def.DeveloperName)
	if def.DeveloperName == "" {
		def.DeveloperName = strings.TrimSpace(component.BusinessKey)
	}
	if def.DeveloperName == "" {
		return sourceBackedEmailTemplateDefinition{}, sourceBackedEmailTemplateProjection{}, fmt.Errorf("email_template %s: developerName or businessKey is required", componentPayloadLabel(component))
	}
	businessKey := strings.TrimSpace(component.BusinessKey)
	if businessKey != "" && !strings.EqualFold(businessKey, def.DeveloperName) {
		return sourceBackedEmailTemplateDefinition{}, sourceBackedEmailTemplateProjection{}, fmt.Errorf("email_template businessKey %q must match developerName %q", businessKey, def.DeveloperName)
	}
	if def.Name == "" {
		def.Name = def.DeveloperName
	}
	if strings.TrimSpace(def.Subject) == "" {
		return sourceBackedEmailTemplateDefinition{}, sourceBackedEmailTemplateProjection{}, fmt.Errorf("email_template %s: subject is required", def.DeveloperName)
	}
	if strings.TrimSpace(def.HTMLValue) == "" && strings.TrimSpace(def.Body) == "" {
		return sourceBackedEmailTemplateDefinition{}, sourceBackedEmailTemplateProjection{}, fmt.Errorf("email_template %s: htmlValue or body is required", def.DeveloperName)
	}
	def.FolderID = trimmedStringPtr(def.FolderID)
	def.Description = trimmedStringPtr(def.Description)
	def.TemplateType = strings.TrimSpace(def.TemplateType)
	if def.TemplateType == "" {
		def.TemplateType = "custom"
	}
	isActive := true
	if def.IsActive != nil {
		isActive = *def.IsActive
	}
	if err := validateSourceBackedEmailTemplateNativeSyntax(def); err != nil {
		return sourceBackedEmailTemplateDefinition{}, sourceBackedEmailTemplateProjection{}, err
	}
	projection := sourceBackedEmailTemplateProjection{
		ID:            sourceBackedEmailTemplateID(packName, def.DeveloperName),
		OwnerPack:     packName,
		Name:          def.Name,
		DeveloperName: def.DeveloperName,
		Subject:       def.Subject,
		HTMLValue:     def.HTMLValue,
		Body:          def.Body,
		FolderID:      def.FolderID,
		Description:   def.Description,
		TemplateType:  def.TemplateType,
		IsActive:      isActive,
	}
	return def, projection, nil
}

func validateSourceBackedEmailTemplateNativeSyntax(def sourceBackedEmailTemplateDefinition) error {
	fields := map[string]string{
		"subject":   def.Subject,
		"htmlValue": def.HTMLValue,
		"body":      def.Body,
	}
	for field, value := range fields {
		if err := validateSourceBackedEmailTemplateFieldSyntax(field, value); err != nil {
			return fmt.Errorf("email_template %s %s: %w", def.DeveloperName, field, err)
		}
	}
	return nil
}

func validateSourceBackedEmailTemplateFieldSyntax(fieldName string, text string) error {
	if strings.Contains(text, "{!") {
		return fmt.Errorf("Salesforce merge syntax is not supported; use native {{Record.*}}, {{User.*}}, or {{Variables.*}} tokens")
	}
	remaining := text
	for {
		open := strings.Index(remaining, "{{")
		closeBeforeOpen := strings.Index(remaining, "}}")
		if open < 0 {
			if closeBeforeOpen >= 0 {
				return fmt.Errorf("unmatched closing token in %s", fieldName)
			}
			return nil
		}
		if closeBeforeOpen >= 0 && closeBeforeOpen < open {
			return fmt.Errorf("unmatched closing token in %s", fieldName)
		}
		afterOpen := remaining[open+2:]
		close := strings.Index(afterOpen, "}}")
		if close < 0 {
			return fmt.Errorf("unmatched opening token in %s", fieldName)
		}
		token := strings.TrimSpace(afterOpen[:close])
		if token == "" {
			return fmt.Errorf("empty merge token")
		}
		if strings.ContainsAny(token, "{}!") {
			return fmt.Errorf("malformed merge token %q", token)
		}
		parts := strings.Split(token, ".")
		if len(parts) != 2 || strings.TrimSpace(parts[0]) == "" || strings.TrimSpace(parts[1]) == "" {
			return fmt.Errorf("token %q must use exactly one approved root and field name", token)
		}
		root := strings.TrimSpace(parts[0])
		path := strings.TrimSpace(parts[1])
		if root != parts[0] || path != parts[1] {
			return fmt.Errorf("token %q has leading or trailing whitespace in a segment", token)
		}
		switch root {
		case "Record", "User", "Variables":
		default:
			return fmt.Errorf("token %q uses unsupported root %q; allowed roots are Record, User, Variables", token, root)
		}
		remaining = afterOpen[close+2:]
	}
}

func upsertSourceBackedEmailTemplate(ctx context.Context, db *sql.DB, row sourceBackedEmailTemplateProjection) error {
	if db == nil {
		return fmt.Errorf("database is required")
	}
	_, err := db.ExecContext(ctx, `
INSERT INTO _System_EmailTemplate
  (Id, Name, Subject, HtmlValue, Body, FolderId, Description, TemplateType, DeveloperName, OwnerPack, IsActive, IsDeleted, CreatedDate, LastModifiedDate)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, NOW(), NOW())
ON DUPLICATE KEY UPDATE
  Id = VALUES(Id),
  Name = VALUES(Name),
  Subject = VALUES(Subject),
  HtmlValue = VALUES(HtmlValue),
  Body = VALUES(Body),
  FolderId = VALUES(FolderId),
  Description = VALUES(Description),
  TemplateType = VALUES(TemplateType),
  OwnerPack = VALUES(OwnerPack),
  IsActive = VALUES(IsActive),
  IsDeleted = 0,
  LastModifiedDate = NOW()`,
		row.ID,
		row.Name,
		row.Subject,
		row.HTMLValue,
		row.Body,
		nullableStringArg(row.FolderID),
		nullableStringArg(row.Description),
		row.TemplateType,
		row.DeveloperName,
		row.OwnerPack,
		row.IsActive,
	)
	if err != nil {
		return fmt.Errorf("upsert _System_EmailTemplate %s/%s: %w", row.OwnerPack, row.DeveloperName, err)
	}
	return nil
}

func liveSourceBackedEmailTemplateProjection(ctx context.Context, db *sql.DB, ownerPack, developerName string) (any, error) {
	if db == nil {
		return nil, fmt.Errorf("database is required")
	}
	var row sourceBackedEmailTemplateProjection
	var folderID, description sql.NullString
	err := db.QueryRowContext(ctx, `
SELECT Id, COALESCE(OwnerPack, ''), Name, DeveloperName, Subject, COALESCE(HtmlValue, ''), COALESCE(Body, ''), FolderId, Description, COALESCE(TemplateType, ''), COALESCE(IsActive, 0)
FROM _System_EmailTemplate
WHERE COALESCE(OwnerPack, '') = ? AND DeveloperName = ? AND COALESCE(IsDeleted, 0) = 0
LIMIT 1`, strings.TrimSpace(ownerPack), strings.TrimSpace(developerName)).Scan(
		&row.ID,
		&row.OwnerPack,
		&row.Name,
		&row.DeveloperName,
		&row.Subject,
		&row.HTMLValue,
		&row.Body,
		&folderID,
		&description,
		&row.TemplateType,
		&row.IsActive,
	)
	if err == sql.ErrNoRows {
		return nil, fmt.Errorf("email_template %s/%s: row missing or soft-deleted", ownerPack, developerName)
	}
	if err != nil {
		return nil, fmt.Errorf("query email_template %s/%s: %w", ownerPack, developerName, err)
	}
	row.FolderID = sourceBackedNullStringPtr(folderID)
	row.Description = sourceBackedNullStringPtr(description)
	return row, nil
}

func sourceBackedEmailTemplateID(ownerPack, developerName string) string {
	sum := sha1.Sum([]byte(strings.ToLower(strings.TrimSpace(ownerPack)) + "\x00" + strings.ToLower(strings.TrimSpace(developerName))))
	return "email_template_" + hex.EncodeToString(sum[:])
}

func validateSourceBackedEmailTemplateDeveloperNames(developerNames []string) error {
	seen := map[string]string{}
	for _, developerName := range developerNames {
		trimmed := strings.TrimSpace(developerName)
		if trimmed == "" {
			continue
		}
		key := strings.ToLower(trimmed)
		if prev, ok := seen[key]; ok {
			return fmt.Errorf("email_template developerName %q is declared more than once in this pack (previous declaration %q)", trimmed, prev)
		}
		seen[key] = trimmed
	}
	return nil
}

func nullableStringArg(value *string) any {
	if value == nil {
		return nil
	}
	return *value
}
