package workbench

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

	"github.com/jackc/pgx/v5"
)

type PromptCategory struct {
	ID            string    `json:"id"`
	Name          string    `json:"name"`
	Description   string    `json:"description"`
	TemplateCount int       `json:"template_count"`
	CreatedAt     time.Time `json:"created_at"`
}

func (s *Service) ListPromptCategories(ctx context.Context) ([]PromptCategory, error) {
	rows, err := s.pool.Query(ctx, `SELECT c.id::text,c.name,c.description,count(t.id)::int,c.created_at FROM gateway.prompt_categories c LEFT JOIN gateway.prompt_templates t ON t.category_id=c.id GROUP BY c.id ORDER BY c.name`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []PromptCategory{}
	for rows.Next() {
		var item PromptCategory
		if err := rows.Scan(&item.ID, &item.Name, &item.Description, &item.TemplateCount, &item.CreatedAt); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func (s *Service) CreatePromptCategory(ctx context.Context, name, description, actorID string) (PromptCategory, error) {
	name, description = strings.TrimSpace(name), strings.TrimSpace(description)
	if name == "" || len(name) > 64 || len(description) > 512 {
		return PromptCategory{}, errors.New("分类名称或描述格式无效")
	}
	id, err := newUUID()
	if err != nil {
		return PromptCategory{}, err
	}
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return PromptCategory{}, err
	}
	defer rollback(ctx, tx)
	var item PromptCategory
	err = tx.QueryRow(ctx, `INSERT INTO gateway.prompt_categories(id,name,description) VALUES($1,$2,$3) RETURNING id::text,name,description,created_at`, id, name, description).Scan(&item.ID, &item.Name, &item.Description, &item.CreatedAt)
	if err != nil {
		return PromptCategory{}, err
	}
	if err = emit(ctx, tx, "prompt_category.created", "prompt_category", id, actorID, nil); err != nil {
		return PromptCategory{}, err
	}
	if err = tx.Commit(ctx); err != nil {
		return PromptCategory{}, err
	}
	return item, nil
}

func (s *Service) DeletePromptCategory(ctx context.Context, id, actorID string) error {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return err
	}
	defer rollback(ctx, tx)
	if _, err = tx.Exec(ctx, `UPDATE gateway.prompt_templates SET category_id=NULL,revision=revision+1,updated_at=clock_timestamp() WHERE category_id=$1`, id); err != nil {
		return err
	}
	tag, err := tx.Exec(ctx, `DELETE FROM gateway.prompt_categories WHERE id=$1`, id)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrNotFound
	}
	if err = emit(ctx, tx, "prompt_category.deleted", "prompt_category", id, actorID, nil); err != nil {
		return err
	}
	return tx.Commit(ctx)
}

func validateVariables(values []Variable) ([]Variable, error) {
	seen := map[string]struct{}{}
	for i := range values {
		values[i].Name = strings.TrimSpace(values[i].Name)
		values[i].Label = strings.TrimSpace(values[i].Label)
		if !regexpVariableName(values[i].Name) {
			return nil, fmt.Errorf("变量名 %q 格式无效", values[i].Name)
		}
		if _, ok := seen[values[i].Name]; ok {
			return nil, fmt.Errorf("变量 %q 重复", values[i].Name)
		}
		seen[values[i].Name] = struct{}{}
		if len(values[i].Default) > 10000 || len(values[i].Label) > 128 {
			return nil, errors.New("变量定义过长")
		}
	}
	if len(values) > 100 {
		return nil, errors.New("变量最多 100 个")
	}
	return values, nil
}

func regexpVariableName(value string) bool {
	if value == "" {
		return false
	}
	matched := variableRE.FindStringSubmatch("{{" + value + "}}")
	return len(matched) == 2 && matched[1] == value
}

func validatePromptInput(input *PromptInput, create bool) error {
	input.Name = strings.TrimSpace(input.Name)
	input.Description = strings.TrimSpace(input.Description)
	input.Content = strings.TrimSpace(input.Content)
	input.ChangeNote = strings.TrimSpace(input.ChangeNote)
	if input.Name == "" || len(input.Name) > 128 || len(input.Description) > 4000 {
		return errors.New("Prompt 名称或描述格式无效")
	}
	if create && (input.Content == "" || len(input.Content) > 100000) {
		return errors.New("Prompt 正文不能为空且最多 100000 字符")
	}
	var err error
	if input.Tags, err = normalizeStrings(input.Tags, 30); err != nil {
		return err
	}
	if input.DepartmentIDs, err = normalizeStrings(input.DepartmentIDs, 100); err != nil {
		return err
	}
	if input.Variables, err = validateVariables(input.Variables); err != nil {
		return err
	}
	return nil
}

const promptSelect = `SELECT t.id::text,t.name,t.description,t.category_id::text,coalesce(c.name,''),t.tags,t.department_ids::text[],t.enabled,t.current_version,t.revision,t.created_at,t.updated_at,
v.id::text,v.template_id::text,v.version,v.content,v.variables,v.change_note,v.created_at
FROM gateway.prompt_templates t LEFT JOIN gateway.prompt_categories c ON c.id=t.category_id LEFT JOIN gateway.prompt_versions v ON v.template_id=t.id AND v.version=t.current_version`

func scanPrompt(row pgx.Row) (PromptTemplate, error) {
	var p PromptTemplate
	var vID, vTemplate *string
	var vVersion *int
	var content, change *string
	var variables []byte
	var versionCreated *time.Time
	err := row.Scan(&p.ID, &p.Name, &p.Description, &p.CategoryID, &p.CategoryName, &p.Tags, &p.DepartmentIDs, &p.Enabled, &p.CurrentVersion, &p.Revision, &p.CreatedAt, &p.UpdatedAt, &vID, &vTemplate, &vVersion, &content, &variables, &change, &versionCreated)
	if err != nil {
		return p, mapNotFound(err)
	}
	if vID != nil {
		v := PromptVersion{ID: *vID, TemplateID: *vTemplate, Version: *vVersion, Content: *content, ChangeNote: *change, CreatedAt: *versionCreated}
		_ = json.Unmarshal(variables, &v.Variables)
		p.Current = &v
	}
	return p, nil
}

func (s *Service) ListPrompts(ctx context.Context) ([]PromptTemplate, error) {
	rows, err := s.pool.Query(ctx, promptSelect+` ORDER BY t.updated_at DESC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []PromptTemplate{}
	for rows.Next() {
		p, err := scanPrompt(rows)
		if err != nil {
			return nil, err
		}
		items = append(items, p)
	}
	return items, rows.Err()
}

func (s *Service) GetPrompt(ctx context.Context, id string) (PromptTemplate, error) {
	return scanPrompt(s.pool.QueryRow(ctx, promptSelect+` WHERE t.id=$1`, id))
}

func (s *Service) CreatePrompt(ctx context.Context, input PromptInput, actorID string) (PromptTemplate, error) {
	if err := validatePromptInput(&input, true); err != nil {
		return PromptTemplate{}, err
	}
	if input.Variables == nil {
		input.Variables = []Variable{}
	}
	id, err := newUUID()
	if err != nil {
		return PromptTemplate{}, err
	}
	versionID, err := newUUID()
	if err != nil {
		return PromptTemplate{}, err
	}
	vars, _ := json.Marshal(input.Variables)
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return PromptTemplate{}, err
	}
	defer rollback(ctx, tx)
	_, err = tx.Exec(ctx, `INSERT INTO gateway.prompt_templates(id,name,description,category_id,tags,department_ids,enabled,created_by) VALUES($1,$2,$3,$4,$5,$6,$7,$8)`, id, input.Name, input.Description, input.CategoryID, input.Tags, input.DepartmentIDs, input.Enabled, actorID)
	if err != nil {
		return PromptTemplate{}, err
	}
	_, err = tx.Exec(ctx, `INSERT INTO gateway.prompt_versions(id,template_id,version,content,variables,change_note,created_by) VALUES($1,$2,1,$3,$4,$5,$6)`, versionID, id, input.Content, vars, input.ChangeNote, actorID)
	if err != nil {
		return PromptTemplate{}, err
	}
	_, err = tx.Exec(ctx, `UPDATE gateway.prompt_templates SET current_version=1 WHERE id=$1`, id)
	if err != nil {
		return PromptTemplate{}, err
	}
	if err = emit(ctx, tx, "prompt.created", "prompt", id, actorID, map[string]any{"version": 1}); err != nil {
		return PromptTemplate{}, err
	}
	if err = tx.Commit(ctx); err != nil {
		return PromptTemplate{}, err
	}
	return s.GetPrompt(ctx, id)
}

func (s *Service) UpdatePrompt(ctx context.Context, id string, input PromptInput, actorID string) (PromptTemplate, error) {
	if err := validatePromptInput(&input, false); err != nil {
		return PromptTemplate{}, err
	}
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return PromptTemplate{}, err
	}
	defer rollback(ctx, tx)
	tag, err := tx.Exec(ctx, `UPDATE gateway.prompt_templates SET name=$2,description=$3,category_id=$4,tags=$5,department_ids=$6,enabled=$7,revision=revision+1,updated_at=clock_timestamp() WHERE id=$1`, id, input.Name, input.Description, input.CategoryID, input.Tags, input.DepartmentIDs, input.Enabled)
	if err != nil {
		return PromptTemplate{}, err
	}
	if tag.RowsAffected() == 0 {
		return PromptTemplate{}, ErrNotFound
	}
	if err = emit(ctx, tx, "prompt.updated", "prompt", id, actorID, nil); err != nil {
		return PromptTemplate{}, err
	}
	if err = tx.Commit(ctx); err != nil {
		return PromptTemplate{}, err
	}
	return s.GetPrompt(ctx, id)
}

func (s *Service) AddPromptVersion(ctx context.Context, id, content string, variables []Variable, changeNote, actorID string, activate bool) (PromptVersion, error) {
	content = strings.TrimSpace(content)
	changeNote = strings.TrimSpace(changeNote)
	if content == "" || len(content) > 100000 {
		return PromptVersion{}, errors.New("Prompt 正文不能为空且最多 100000 字符")
	}
	variables, err := validateVariables(variables)
	if err != nil {
		return PromptVersion{}, err
	}
	if variables == nil {
		variables = []Variable{}
	}
	raw, _ := json.Marshal(variables)
	versionID, err := newUUID()
	if err != nil {
		return PromptVersion{}, err
	}
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return PromptVersion{}, err
	}
	defer rollback(ctx, tx)
	var lockedID string
	if err = tx.QueryRow(ctx, `SELECT id::text FROM gateway.prompt_templates WHERE id=$1 FOR UPDATE`, id).Scan(&lockedID); errors.Is(err, pgx.ErrNoRows) {
		return PromptVersion{}, ErrNotFound
	} else if err != nil {
		return PromptVersion{}, err
	}
	var version int
	err = tx.QueryRow(ctx, `SELECT coalesce(max(version),0)+1 FROM gateway.prompt_versions WHERE template_id=$1`, id).Scan(&version)
	if err != nil {
		return PromptVersion{}, err
	}
	var created time.Time
	err = tx.QueryRow(ctx, `INSERT INTO gateway.prompt_versions(id,template_id,version,content,variables,change_note,created_by) SELECT $1,id,$3,$4,$5,$6,$7 FROM gateway.prompt_templates WHERE id=$2 RETURNING created_at`, versionID, id, version, content, raw, changeNote, actorID).Scan(&created)
	if errors.Is(err, pgx.ErrNoRows) {
		return PromptVersion{}, ErrNotFound
	}
	if err != nil {
		return PromptVersion{}, err
	}
	if activate {
		if _, err = tx.Exec(ctx, `UPDATE gateway.prompt_templates SET current_version=$2,revision=revision+1,updated_at=clock_timestamp() WHERE id=$1`, id, version); err != nil {
			return PromptVersion{}, err
		}
	}
	if err = emit(ctx, tx, "prompt.version_created", "prompt", id, actorID, map[string]any{"version": version, "active": activate}); err != nil {
		return PromptVersion{}, err
	}
	if err = tx.Commit(ctx); err != nil {
		return PromptVersion{}, err
	}
	return PromptVersion{ID: versionID, TemplateID: id, Version: version, Content: content, Variables: variables, ChangeNote: changeNote, CreatedAt: created}, nil
}

func (s *Service) ListPromptVersions(ctx context.Context, id string) ([]PromptVersion, error) {
	rows, err := s.pool.Query(ctx, `SELECT id::text,template_id::text,version,content,variables,change_note,created_at FROM gateway.prompt_versions WHERE template_id=$1 ORDER BY version DESC`, id)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []PromptVersion{}
	for rows.Next() {
		var v PromptVersion
		var raw []byte
		if err = rows.Scan(&v.ID, &v.TemplateID, &v.Version, &v.Content, &raw, &v.ChangeNote, &v.CreatedAt); err != nil {
			return nil, err
		}
		_ = json.Unmarshal(raw, &v.Variables)
		items = append(items, v)
	}
	return items, rows.Err()
}

func (s *Service) ActivatePromptVersion(ctx context.Context, id string, version int, actorID string) error {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return err
	}
	defer rollback(ctx, tx)
	tag, err := tx.Exec(ctx, `UPDATE gateway.prompt_templates t SET current_version=$2,revision=revision+1,updated_at=clock_timestamp() WHERE id=$1 AND EXISTS(SELECT 1 FROM gateway.prompt_versions v WHERE v.template_id=t.id AND v.version=$2)`, id, version)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrNotFound
	}
	if err = emit(ctx, tx, "prompt.version_activated", "prompt", id, actorID, map[string]any{"version": version}); err != nil {
		return err
	}
	return tx.Commit(ctx)
}

func (s *Service) DeletePrompt(ctx context.Context, id, actorID string) error {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return err
	}
	defer rollback(ctx, tx)
	tag, err := tx.Exec(ctx, `DELETE FROM gateway.prompt_templates WHERE id=$1`, id)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrNotFound
	}
	if err = emit(ctx, tx, "prompt.deleted", "prompt", id, actorID, nil); err != nil {
		return err
	}
	return tx.Commit(ctx)
}

func (s *Service) RenderPromptByName(ctx context.Context, name string, provided map[string]any) (PromptVersion, string, error) {
	var v PromptVersion
	var raw []byte
	err := s.pool.QueryRow(ctx, `SELECT v.id::text,v.template_id::text,v.version,v.content,v.variables,v.change_note,v.created_at FROM gateway.prompt_templates t JOIN gateway.prompt_versions v ON v.template_id=t.id AND v.version=t.current_version WHERE t.name=$1 AND t.enabled`, name).Scan(&v.ID, &v.TemplateID, &v.Version, &v.Content, &raw, &v.ChangeNote, &v.CreatedAt)
	if err != nil {
		return v, "", mapNotFound(err)
	}
	_ = json.Unmarshal(raw, &v.Variables)
	rendered, err := RenderPrompt(v.Content, v.Variables, provided)
	return v, rendered, err
}
