package draftsvc

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"qctextbuilder/internal/domain"
	"qctextbuilder/internal/store"
)

type UpsertDraftRequest struct {
	DraftID         string                       `json:"draftId,omitempty"`
	TemplateID      *int64                       `json:"templateId,omitempty"`
	ManifestID      string                       `json:"manifestId,omitempty"`
	Source          string                       `json:"source,omitempty"`
	RequestName     string                       `json:"requestName,omitempty"`
	GlobalData      map[string]any               `json:"globalData"`
	FieldValues     map[string]string            `json:"fieldValues"`
	DraftContext    *domain.DraftContext         `json:"draftContext,omitempty"`
	SuggestionState *domain.DraftSuggestionState `json:"suggestionState,omitempty"`
	Status          string                       `json:"status,omitempty"`
	Notes           string                       `json:"notes,omitempty"`
}

type Service struct {
	drafts    store.DraftStore
	templates store.TemplateStore
	manifests store.ManifestStore
}

func New(draftStore store.DraftStore, templateStore store.TemplateStore, manifestStore store.ManifestStore) *Service {
	return &Service{
		drafts:    draftStore,
		templates: templateStore,
		manifests: manifestStore,
	}
}

func (s *Service) SaveDraft(ctx context.Context, req UpsertDraftRequest) (*domain.BuildDraft, error) {
	templateID := int64(0)
	if req.TemplateID != nil {
		templateID = *req.TemplateID
	}
	var existing *domain.BuildDraft
	if strings.TrimSpace(req.DraftID) != "" {
		var err error
		existing, err = s.drafts.GetDraftByID(ctx, strings.TrimSpace(req.DraftID))
		if err != nil {
			return nil, fmt.Errorf("get draft: %w", err)
		}
		if templateID == 0 {
			templateID = existing.TemplateID
		}
	}

	if templateID > 0 {
		template, err := s.templates.GetTemplateByID(ctx, templateID)
		if err != nil {
			return nil, fmt.Errorf("get template: %w", err)
		}
		if !template.IsAITemplate {
			return nil, errors.New("only ai templates are allowed")
		}
	}

	manifestID := strings.TrimSpace(req.ManifestID)
	if templateID <= 0 {
		manifestID = ""
	} else if manifestID == "" {
		manifest, err := s.manifests.GetActiveManifestByTemplateID(ctx, templateID)
		if err != nil {
			return nil, fmt.Errorf("get active manifest: %w", err)
		}
		manifestID = manifest.ID
	}

	globalDataJSON, err := json.Marshal(req.GlobalData)
	if err != nil {
		return nil, errors.New("globalData is invalid JSON")
	}
	fieldValuesJSON, err := json.Marshal(req.FieldValues)
	if err != nil {
		return nil, errors.New("fieldValues is invalid JSON")
	}
	draftContextJSON, err := buildDraftContextJSON(req, existing)
	if err != nil {
		return nil, err
	}
	suggestionStateJSON, err := buildSuggestionStateJSON(req, existing)
	if err != nil {
		return nil, err
	}

	now := time.Now().UTC()
	source := defaultString(req.Source, "ui")
	status := normalizeDraftStatus(req.Status)
	if status == "" {
		status = "draft"
	}

	draft := domain.BuildDraft{
		ID:                  strings.TrimSpace(req.DraftID),
		TemplateID:          templateID,
		ManifestID:          manifestID,
		Source:              source,
		RequestName:         strings.TrimSpace(req.RequestName),
		GlobalDataJSON:      globalDataJSON,
		FieldValuesJSON:     fieldValuesJSON,
		DraftContextJSON:    draftContextJSON,
		SuggestionStateJSON: suggestionStateJSON,
		Status:              status,
		Notes:               strings.TrimSpace(req.Notes),
		UpdatedAt:           now,
	}
	if draft.ID == "" {
		draft.ID = strconv.FormatInt(time.Now().UnixNano(), 10)
		draft.CreatedAt = now
		if err := s.drafts.CreateDraft(ctx, draft); err != nil {
			return nil, fmt.Errorf("create draft: %w", err)
		}
	} else {
		existing, err := s.drafts.GetDraftByID(ctx, draft.ID)
		if err != nil {
			return nil, fmt.Errorf("get draft: %w", err)
		}
		draft.CreatedAt = existing.CreatedAt
		if err := s.drafts.UpdateDraft(ctx, draft); err != nil {
			return nil, fmt.Errorf("update draft: %w", err)
		}
	}

	return s.drafts.GetDraftByID(ctx, draft.ID)
}

func buildDraftContextJSON(req UpsertDraftRequest, existing *domain.BuildDraft) (json.RawMessage, error) {
	if req.DraftContext == nil {
		if existing != nil {
			return existing.DraftContextJSON, nil
		}
		return nil, nil
	}
	raw, err := json.Marshal(req.DraftContext)
	if err != nil {
		return nil, errors.New("draftContext is invalid JSON")
	}
	return raw, nil
}

func buildSuggestionStateJSON(req UpsertDraftRequest, existing *domain.BuildDraft) (json.RawMessage, error) {
	if req.SuggestionState == nil {
		if existing != nil {
			return existing.SuggestionStateJSON, nil
		}
		return nil, nil
	}
	raw, err := json.Marshal(req.SuggestionState)
	if err != nil {
		return nil, errors.New("suggestionState is invalid JSON")
	}
	return raw, nil
}

func (s *Service) GetDraft(ctx context.Context, draftID string) (*domain.BuildDraft, error) {
	return s.drafts.GetDraftByID(ctx, strings.TrimSpace(draftID))
}

func (s *Service) ListDrafts(ctx context.Context, limit int) ([]domain.BuildDraft, error) {
	if limit <= 0 {
		limit = 50
	}
	return s.drafts.ListDrafts(ctx, limit)
}

func normalizeDraftStatus(status string) string {
	switch strings.ToLower(strings.TrimSpace(status)) {
	case "":
		return ""
	case "draft", "reviewed", "submitted":
		return strings.ToLower(strings.TrimSpace(status))
	default:
		return "draft"
	}
}

func defaultString(value, fallback string) string {
	if strings.TrimSpace(value) == "" {
		return fallback
	}
	return strings.TrimSpace(value)
}
