package handlers

import (
	"encoding/json"
	"net/http"
	"strconv"
	"strings"

	"github.com/go-chi/chi/v5"

	"qctextbuilder/internal/buildsvc"
	"qctextbuilder/internal/domain"
	"qctextbuilder/internal/draftsvc"
	"qctextbuilder/internal/logging"
	"qctextbuilder/internal/onboarding"
	"qctextbuilder/internal/templatesvc"
)

type API struct {
	templateSvc *templatesvc.Service
	onboardSvc  *onboarding.Service
	draftSvc    *draftsvc.Service
	buildSvc    buildsvc.Service
	recentLogs  *logging.RecentStore
}

func NewAPI(templateSvc *templatesvc.Service, onboardSvc *onboarding.Service, draftSvc *draftsvc.Service, buildSvc buildsvc.Service, recentLogs *logging.RecentStore) *API {
	return &API{
		templateSvc: templateSvc,
		onboardSvc:  onboardSvc,
		draftSvc:    draftSvc,
		buildSvc:    buildSvc,
		recentLogs:  recentLogs,
	}
}

func (a *API) Health(w http.ResponseWriter, _ *http.Request) {
	writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
}

func (a *API) SyncTemplates(w http.ResponseWriter, r *http.Request) {
	templates, err := a.templateSvc.SyncAITemplates(r.Context())
	if err != nil {
		writeJSON(w, http.StatusBadGateway, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(templates), "templates": templates})
}

func (a *API) ListTemplates(w http.ResponseWriter, r *http.Request) {
	templates, err := a.templateSvc.ListTemplates(r.Context())
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(templates), "templates": templates})
}

func (a *API) GetTemplateDetail(w http.ResponseWriter, r *http.Request) {
	rawID := chi.URLParam(r, "id")
	templateID, err := strconv.ParseInt(rawID, 10, 64)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"})
		return
	}

	detail, err := a.templateSvc.GetTemplateDetail(r.Context(), templateID)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, detail)
}

func (a *API) OnboardTemplate(w http.ResponseWriter, r *http.Request) {
	rawID := chi.URLParam(r, "id")
	templateID, err := strconv.ParseInt(rawID, 10, 64)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"})
		return
	}

	manifest, fields, err := a.onboardSvc.OnboardTemplate(r.Context(), templateID)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"manifestId": manifest.ID,
		"fieldCount": len(fields),
		"status":     "reviewed",
	})
}

type updateTemplateFieldsRequest struct {
	ManifestID string                    `json:"manifestId"`
	Fields     []updateTemplateFieldItem `json:"fields"`
}

type updateTemplateFieldItem struct {
	Path           string  `json:"path"`
	IsEnabled      *bool   `json:"isEnabled,omitempty"`
	IsRequiredByUs *bool   `json:"isRequiredByUs,omitempty"`
	DisplayLabel   *string `json:"displayLabel,omitempty"`
	DisplayOrder   *int    `json:"displayOrder,omitempty"`
	WebsiteSection *string `json:"websiteSection,omitempty"`
	Notes          *string `json:"notes,omitempty"`
}

func (a *API) UpdateTemplateFields(w http.ResponseWriter, r *http.Request) {
	rawID := chi.URLParam(r, "id")
	templateID, err := strconv.ParseInt(rawID, 10, 64)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"})
		return
	}

	var req updateTemplateFieldsRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
		return
	}
	if len(req.Fields) == 0 {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "fields is required"})
		return
	}

	patches := make([]onboarding.FieldPatch, 0, len(req.Fields))
	for _, f := range req.Fields {
		patches = append(patches, onboarding.FieldPatch{
			Path:           f.Path,
			IsEnabled:      f.IsEnabled,
			IsRequiredByUs: f.IsRequiredByUs,
			DisplayLabel:   f.DisplayLabel,
			DisplayOrder:   f.DisplayOrder,
			WebsiteSection: f.WebsiteSection,
			Notes:          f.Notes,
		})
	}

	manifest, fields, err := a.onboardSvc.UpdateTemplateFields(r.Context(), templateID, req.ManifestID, patches)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"templateId": templateID,
		"manifestId": manifest.ID,
		"fieldCount": len(fields),
		"fields":     fields,
	})
}

func (a *API) StartBuild(w http.ResponseWriter, r *http.Request) {
	var req buildsvc.StartBuildRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
		return
	}

	result, err := a.buildSvc.StartBuild(r.Context(), req)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusAccepted, result)
}

type upsertDraftRequest struct {
	TemplateID      *int64                       `json:"templateId,omitempty"`
	ManifestID      string                       `json:"manifestId"`
	Source          string                       `json:"source"`
	RequestName     string                       `json:"requestName"`
	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"`
	Notes           string                       `json:"notes"`
}

type intakeDraftRequest struct {
	DraftID            string                    `json:"draftId,omitempty"`
	Source             string                    `json:"source"`
	RequestName        string                    `json:"requestName"`
	TemplateID         *int64                    `json:"templateId,omitempty"`
	GlobalData         map[string]any            `json:"globalData"`
	Notes              string                    `json:"notes"`
	WebsiteURL         string                    `json:"websiteUrl,omitempty"`
	WebsiteSummary     string                    `json:"websiteSummary,omitempty"`
	BusinessType       string                    `json:"businessType,omitempty"`
	LocaleStyle        string                    `json:"localeStyle,omitempty"`
	MarketStyle        string                    `json:"marketStyle,omitempty"`
	AddressMode        string                    `json:"addressMode,omitempty"`
	ContentTone        string                    `json:"contentTone,omitempty"`
	PromptInstructions string                    `json:"promptInstructions,omitempty"`
	StyleProfile       *domain.DraftStyleProfile `json:"styleProfile,omitempty"`
}

func (a *API) IntakeDraft(w http.ResponseWriter, r *http.Request) {
	var req intakeDraftRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
		return
	}

	globalData := req.GlobalData
	if globalData == nil {
		globalData = map[string]any{}
	}
	if strings.TrimSpace(req.BusinessType) != "" && strings.TrimSpace(getMapString(globalData, "businessType")) == "" {
		globalData["businessType"] = strings.TrimSpace(req.BusinessType)
	}

	styleProfile := domain.DraftStyleProfile{
		LocaleStyle:        strings.TrimSpace(req.LocaleStyle),
		MarketStyle:        strings.TrimSpace(req.MarketStyle),
		AddressMode:        strings.TrimSpace(req.AddressMode),
		ContentTone:        strings.TrimSpace(req.ContentTone),
		PromptInstructions: strings.TrimSpace(req.PromptInstructions),
	}
	if req.StyleProfile != nil {
		styleProfile = *req.StyleProfile
		if styleProfile.LocaleStyle == "" {
			styleProfile.LocaleStyle = strings.TrimSpace(req.LocaleStyle)
		}
		if styleProfile.MarketStyle == "" {
			styleProfile.MarketStyle = strings.TrimSpace(req.MarketStyle)
		}
		if styleProfile.AddressMode == "" {
			styleProfile.AddressMode = strings.TrimSpace(req.AddressMode)
		}
		if styleProfile.ContentTone == "" {
			styleProfile.ContentTone = strings.TrimSpace(req.ContentTone)
		}
		if styleProfile.PromptInstructions == "" {
			styleProfile.PromptInstructions = strings.TrimSpace(req.PromptInstructions)
		}
	}
	businessType := strings.TrimSpace(req.BusinessType)
	if businessType == "" {
		businessType = strings.TrimSpace(getMapString(globalData, "businessType"))
	}
	draftContext := &domain.DraftContext{
		IntakeSource: strings.TrimSpace(req.Source),
		LLM: domain.DraftLLMContext{
			BusinessType:   businessType,
			WebsiteURL:     strings.TrimSpace(req.WebsiteURL),
			WebsiteSummary: strings.TrimSpace(req.WebsiteSummary),
			StyleProfile:   styleProfile,
		},
	}

	draft, err := a.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{
		DraftID:      strings.TrimSpace(req.DraftID),
		TemplateID:   req.TemplateID,
		Source:       defaultStr(req.Source, "intake-api"),
		RequestName:  req.RequestName,
		GlobalData:   globalData,
		FieldValues:  map[string]string{},
		DraftContext: draftContext,
		Status:       "draft",
		Notes:        req.Notes,
	})
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}
	if strings.TrimSpace(req.DraftID) == "" {
		writeJSON(w, http.StatusCreated, draft)
		return
	}
	writeJSON(w, http.StatusOK, draft)
}

func (a *API) ListDrafts(w http.ResponseWriter, r *http.Request) {
	limit, _ := strconv.Atoi(strings.TrimSpace(r.URL.Query().Get("limit")))
	drafts, err := a.draftSvc.ListDrafts(r.Context(), limit)
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(drafts), "drafts": drafts})
}

func (a *API) GetDraft(w http.ResponseWriter, r *http.Request) {
	draftID := strings.TrimSpace(chi.URLParam(r, "id"))
	draft, err := a.draftSvc.GetDraft(r.Context(), draftID)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, draft)
}

func (a *API) UpdateDraft(w http.ResponseWriter, r *http.Request) {
	draftID := strings.TrimSpace(chi.URLParam(r, "id"))
	var req upsertDraftRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
		return
	}
	draft, err := a.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{
		DraftID:         draftID,
		TemplateID:      req.TemplateID,
		ManifestID:      req.ManifestID,
		Source:          req.Source,
		RequestName:     req.RequestName,
		GlobalData:      req.GlobalData,
		FieldValues:     req.FieldValues,
		DraftContext:    req.DraftContext,
		SuggestionState: req.SuggestionState,
		Status:          req.Status,
		Notes:           req.Notes,
	})
	if err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, draft)
}

func (a *API) GetBuild(w http.ResponseWriter, r *http.Request) {
	buildID := chi.URLParam(r, "id")
	build, err := a.buildSvc.GetBuild(r.Context(), buildID)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, build)
}

func (a *API) PollBuildOnce(w http.ResponseWriter, r *http.Request) {
	buildID := chi.URLParam(r, "id")
	if err := a.buildSvc.PollOnce(r.Context(), buildID); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}

	build, err := a.buildSvc.GetBuild(r.Context(), buildID)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, build)
}

func (a *API) FetchBuildEditorURL(w http.ResponseWriter, r *http.Request) {
	buildID := chi.URLParam(r, "id")
	if err := a.buildSvc.FetchEditorURL(r.Context(), buildID); err != nil {
		writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
		return
	}

	build, err := a.buildSvc.GetBuild(r.Context(), buildID)
	if err != nil {
		writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, build)
}

func (a *API) ListLogs(w http.ResponseWriter, r *http.Request) {
	limit, _ := strconv.Atoi(strings.TrimSpace(r.URL.Query().Get("limit")))
	if limit <= 0 {
		limit = 100
	}
	if limit > 500 {
		limit = 500
	}
	logs := a.recentLogs.List(limit)
	writeJSON(w, http.StatusOK, map[string]any{
		"count": len(logs),
		"logs":  logs,
	})
}

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(v)
}

func defaultStr(v, fallback string) string {
	if strings.TrimSpace(v) == "" {
		return fallback
	}
	return strings.TrimSpace(v)
}

func getMapString(values map[string]any, key string) string {
	if values == nil {
		return ""
	}
	raw, _ := values[key].(string)
	return raw
}
