package memory

import (
	"context"
	"encoding/json"
	"errors"
	"sort"
	"sync"
	"time"

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

type Store struct {
	mu            sync.RWMutex
	templates     map[int64]domain.Template
	manifests     map[int64]domain.TemplateManifest
	manifestField map[string][]domain.TemplateField
	builds        map[string]domain.SiteBuild
	drafts        map[string]domain.BuildDraft
	settings      domain.AppSettings
	hasSettings   bool
}

func New() *Store {
	return &Store{
		templates:     make(map[int64]domain.Template),
		manifests:     make(map[int64]domain.TemplateManifest),
		manifestField: make(map[string][]domain.TemplateField),
		builds:        make(map[string]domain.SiteBuild),
		drafts:        make(map[string]domain.BuildDraft),
	}
}

func (s *Store) UpsertTemplates(_ context.Context, templates []domain.Template) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	for _, t := range templates {
		existing, ok := s.templates[t.ID]
		if ok {
			t.IsOnboarded = existing.IsOnboarded
			t.ManifestStatus = existing.ManifestStatus
			t.LastDiscoveredAt = existing.LastDiscoveredAt
		}
		s.templates[t.ID] = t
	}
	return nil
}

func (s *Store) GetTemplateByID(_ context.Context, id int64) (*domain.Template, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	t, ok := s.templates[id]
	if !ok {
		return nil, store.ErrNotFound
	}
	copy := t
	return &copy, nil
}

func (s *Store) ListTemplates(_ context.Context) ([]domain.Template, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	out := make([]domain.Template, 0, len(s.templates))
	for _, t := range s.templates {
		out = append(out, t)
	}
	return out, nil
}

func (s *Store) SetTemplateManifestStatus(_ context.Context, templateID int64, status string, onboarded bool) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	t, ok := s.templates[templateID]
	if !ok {
		return store.ErrNotFound
	}
	t.ManifestStatus = status
	t.IsOnboarded = onboarded
	s.templates[templateID] = t
	return nil
}

func (s *Store) CreateManifest(_ context.Context, manifest domain.TemplateManifest, fields []domain.TemplateField) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.manifests[manifest.TemplateID] = manifest
	s.manifestField[manifest.ID] = fields
	return nil
}

func (s *Store) GetActiveManifestByTemplateID(_ context.Context, templateID int64) (*domain.TemplateManifest, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	m, ok := s.manifests[templateID]
	if !ok {
		return nil, store.ErrNotFound
	}
	copy := m
	return &copy, nil
}

func (s *Store) ListFieldsByManifestID(_ context.Context, manifestID string) ([]domain.TemplateField, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	fields, ok := s.manifestField[manifestID]
	if !ok {
		return nil, store.ErrNotFound
	}
	out := make([]domain.TemplateField, 0, len(fields))
	out = append(out, fields...)
	return out, nil
}

func (s *Store) UpdateFields(_ context.Context, manifestID string, fields []domain.TemplateField) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if _, ok := s.manifestField[manifestID]; !ok {
		return store.ErrNotFound
	}
	next := make([]domain.TemplateField, len(fields))
	copy(next, fields)
	s.manifestField[manifestID] = next
	return nil
}

func (s *Store) CreateBuild(_ context.Context, build domain.SiteBuild) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if _, exists := s.builds[build.ID]; exists {
		return errors.New("build already exists")
	}
	s.builds[build.ID] = build
	return nil
}

func (s *Store) GetBuildByID(_ context.Context, id string) (*domain.SiteBuild, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	build, ok := s.builds[id]
	if !ok {
		return nil, store.ErrNotFound
	}
	copy := build
	return &copy, nil
}

func (s *Store) ListBuildsByStatuses(_ context.Context, statuses []string, limit int) ([]domain.SiteBuild, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	allowed := make(map[string]struct{}, len(statuses))
	for _, status := range statuses {
		allowed[status] = struct{}{}
	}

	out := make([]domain.SiteBuild, 0)
	for _, build := range s.builds {
		if len(allowed) > 0 {
			if _, ok := allowed[build.QCStatus]; !ok {
				continue
			}
		}
		out = append(out, build)
		if limit > 0 && len(out) >= limit {
			break
		}
	}
	return out, nil
}

func (s *Store) MarkBuildSubmitted(_ context.Context, buildID string, jobID int64, status string, qcResult json.RawMessage, startedAt time.Time) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	build, ok := s.builds[buildID]
	if !ok {
		return store.ErrNotFound
	}
	build.QCJobID = &jobID
	build.QCStatus = status
	build.QCResultJSON = cloneRaw(qcResult)
	build.StartedAt = &startedAt
	s.builds[buildID] = build
	return nil
}

func (s *Store) UpdateBuildFromJob(_ context.Context, buildID string, status string, siteID *int64, previewURL string, qcResult json.RawMessage, qcError json.RawMessage, finishedAt *time.Time) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	build, ok := s.builds[buildID]
	if !ok {
		return store.ErrNotFound
	}
	build.QCStatus = status
	build.QCResultJSON = cloneRaw(qcResult)
	build.QCErrorJSON = cloneRaw(qcError)
	build.QCPreviewURL = previewURL
	if siteID != nil {
		id := *siteID
		build.QCSiteID = &id
	}
	build.FinishedAt = finishedAt
	s.builds[buildID] = build
	return nil
}

func (s *Store) UpdateBuildEditorURL(_ context.Context, buildID string, editorURL string, qcResult json.RawMessage) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	build, ok := s.builds[buildID]
	if !ok {
		return store.ErrNotFound
	}
	build.QCEditorURL = editorURL
	build.QCResultJSON = cloneRaw(qcResult)
	s.builds[buildID] = build
	return nil
}

func cloneRaw(raw json.RawMessage) json.RawMessage {
	if raw == nil {
		return nil
	}
	out := make([]byte, len(raw))
	copy(out, raw)
	return json.RawMessage(out)
}

func (s *Store) UpsertSettings(_ context.Context, settings domain.AppSettings) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.settings = settings
	s.hasSettings = true
	return nil
}

func (s *Store) GetSettings(_ context.Context) (*domain.AppSettings, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if !s.hasSettings {
		return nil, store.ErrNotFound
	}
	value := s.settings
	return &value, nil
}

func (s *Store) CreateDraft(_ context.Context, draft domain.BuildDraft) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, exists := s.drafts[draft.ID]; exists {
		return errors.New("draft already exists")
	}
	s.drafts[draft.ID] = draft
	return nil
}

func (s *Store) UpdateDraft(_ context.Context, draft domain.BuildDraft) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if _, exists := s.drafts[draft.ID]; !exists {
		return store.ErrNotFound
	}
	s.drafts[draft.ID] = draft
	return nil
}

func (s *Store) GetDraftByID(_ context.Context, id string) (*domain.BuildDraft, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	draft, ok := s.drafts[id]
	if !ok {
		return nil, store.ErrNotFound
	}
	copy := draft
	copy.GlobalDataJSON = cloneRaw(draft.GlobalDataJSON)
	copy.FieldValuesJSON = cloneRaw(draft.FieldValuesJSON)
	copy.DraftContextJSON = cloneRaw(draft.DraftContextJSON)
	copy.SuggestionStateJSON = cloneRaw(draft.SuggestionStateJSON)
	return &copy, nil
}

func (s *Store) ListDrafts(_ context.Context, limit int) ([]domain.BuildDraft, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := make([]domain.BuildDraft, 0, len(s.drafts))
	for _, draft := range s.drafts {
		copy := draft
		copy.GlobalDataJSON = cloneRaw(draft.GlobalDataJSON)
		copy.FieldValuesJSON = cloneRaw(draft.FieldValuesJSON)
		copy.DraftContextJSON = cloneRaw(draft.DraftContextJSON)
		copy.SuggestionStateJSON = cloneRaw(draft.SuggestionStateJSON)
		out = append(out, copy)
	}
	sort.Slice(out, func(i, j int) bool {
		return out[i].UpdatedAt.After(out[j].UpdatedAt)
	})
	if limit > 0 && len(out) > limit {
		out = out[:limit]
	}
	return out, nil
}
