package workbench

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

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

func validateApplication(app *Application) error {
	app.Code = strings.ToLower(strings.TrimSpace(app.Code))
	app.Name = strings.TrimSpace(app.Name)
	app.Description = strings.TrimSpace(app.Description)
	app.Status = strings.TrimSpace(app.Status)
	if !codeRE.MatchString(app.Code) || len(app.Code) < 3 || app.Name == "" || len(app.Name) > 128 || len(app.Description) > 4000 {
		return errors.New("应用编码、名称或描述格式无效")
	}
	switch app.Status {
	case "draft", "active", "suspended", "retired":
	case "":
		app.Status = "draft"
	default:
		return errors.New("应用状态无效")
	}
	var err error
	app.DepartmentIDs, err = normalizeStrings(app.DepartmentIDs, 100)
	return err
}

func normalizeApplicationConfig(config *ApplicationConfig, requireModel bool) error {
	config.Model = strings.TrimSpace(config.Model)
	config.PromptTemplateID = strings.TrimSpace(config.PromptTemplateID)
	if requireModel && config.Model == "" {
		return errors.New("发布配置必须指定模型")
	}
	if len(config.Model) > 255 {
		return errors.New("模型名称过长")
	}
	var err error
	if config.KnowledgeBaseIDs, err = normalizeStrings(config.KnowledgeBaseIDs, 20); err != nil {
		return err
	}
	if config.ToolIDs, err = normalizeStrings(config.ToolIDs, 20); err != nil {
		return err
	}
	if config.RetrievalTopK == 0 {
		config.RetrievalTopK = 4
	}
	if config.RetrievalTopK < 1 || config.RetrievalTopK > 20 {
		return errors.New("retrieval_top_k 应在 1-20 之间")
	}
	if config.Temperature < 0 || config.Temperature > 2 {
		return errors.New("temperature 应在 0-2 之间")
	}
	if config.MaxToolRounds < 0 || config.MaxToolRounds > 8 {
		return errors.New("max_tool_rounds 应在 0-8 之间")
	}
	return nil
}

func (s *Service) validateApplicationRefs(ctx context.Context, config ApplicationConfig) error {
	if config.PromptTemplateID != "" {
		var ok bool
		if err := s.pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM gateway.prompt_templates WHERE id=$1 AND enabled AND current_version IS NOT NULL)`, config.PromptTemplateID).Scan(&ok); err != nil {
			return err
		}
		if !ok {
			return errors.New("绑定的 Prompt 不存在、未启用或没有生效版本")
		}
	}
	for _, id := range config.KnowledgeBaseIDs {
		var ok bool
		if err := s.pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM gateway.knowledge_bases WHERE id=$1 AND enabled)`, id).Scan(&ok); err != nil {
			return err
		}
		if !ok {
			return fmt.Errorf("知识库 %s 不存在或未启用", id)
		}
	}
	for _, id := range config.ToolIDs {
		var ok bool
		if err := s.pool.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM gateway.tool_definitions WHERE id=$1 AND enabled)`, id).Scan(&ok); err != nil {
			return err
		}
		if !ok {
			return fmt.Errorf("工具 %s 不存在或未启用", id)
		}
	}
	return nil
}

const applicationSelect = `SELECT a.id::text,a.code,a.name,a.description,a.department_ids::text[],a.status,a.draft_config,a.published_version,p.config,a.revision,a.created_at,a.updated_at FROM gateway.applications a LEFT JOIN gateway.application_versions p ON p.application_id=a.id AND p.version=a.published_version`

func scanApplication(row pgx.Row) (Application, error) {
	var app Application
	var draft []byte
	var published []byte
	err := row.Scan(&app.ID, &app.Code, &app.Name, &app.Description, &app.DepartmentIDs, &app.Status, &draft, &app.PublishedVersion, &published, &app.Revision, &app.CreatedAt, &app.UpdatedAt)
	if err != nil {
		return app, mapNotFound(err)
	}
	_ = json.Unmarshal(draft, &app.DraftConfig)
	if len(published) > 0 {
		var config ApplicationConfig
		if json.Unmarshal(published, &config) == nil {
			app.PublishedConfig = &config
		}
	}
	return app, nil
}
func (s *Service) ListApplications(ctx context.Context) ([]Application, error) {
	rows, err := s.pool.Query(ctx, applicationSelect+` ORDER BY a.updated_at DESC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []Application{}
	for rows.Next() {
		app, err := scanApplication(rows)
		if err != nil {
			return nil, err
		}
		items = append(items, app)
	}
	return items, rows.Err()
}
func (s *Service) GetApplication(ctx context.Context, id string) (Application, error) {
	return scanApplication(s.pool.QueryRow(ctx, applicationSelect+` WHERE a.id=$1`, id))
}
func (s *Service) GetPublishedApplicationByCode(ctx context.Context, code string) (Application, error) {
	return scanApplication(s.pool.QueryRow(ctx, applicationSelect+` WHERE a.code=$1 AND a.status='active' AND a.published_version IS NOT NULL`, code))
}

func (s *Service) SaveApplication(ctx context.Context, app Application, actorID string, create bool) (Application, error) {
	if err := validateApplication(&app); err != nil {
		return Application{}, err
	}
	if err := normalizeApplicationConfig(&app.DraftConfig, false); err != nil {
		return Application{}, err
	}
	if err := s.validateApplicationRefs(ctx, app.DraftConfig); err != nil {
		return Application{}, err
	}
	raw, _ := json.Marshal(app.DraftConfig)
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return Application{}, err
	}
	defer rollback(ctx, tx)
	if create {
		app.ID, err = newUUID()
		if err != nil {
			return Application{}, err
		}
		_, err = tx.Exec(ctx, `INSERT INTO gateway.applications(id,code,name,description,department_ids,status,draft_config,created_by) VALUES($1,$2,$3,$4,$5,$6,$7,$8)`, app.ID, app.Code, app.Name, app.Description, app.DepartmentIDs, app.Status, raw, actorID)
	} else {
		tag, updateErr := tx.Exec(ctx, `UPDATE gateway.applications SET code=$2,name=$3,description=$4,department_ids=$5,status=$6,draft_config=$7,revision=revision+1,updated_at=clock_timestamp() WHERE id=$1`, app.ID, app.Code, app.Name, app.Description, app.DepartmentIDs, app.Status, raw)
		err = updateErr
		if err == nil && tag.RowsAffected() == 0 {
			return Application{}, ErrNotFound
		}
	}
	if err != nil {
		return Application{}, err
	}
	event := "application.updated"
	if create {
		event = "application.created"
	}
	if err = emit(ctx, tx, event, "application", app.ID, actorID, nil); err != nil {
		return Application{}, err
	}
	if err = tx.Commit(ctx); err != nil {
		return Application{}, err
	}
	return s.GetApplication(ctx, app.ID)
}

func (s *Service) PublishApplication(ctx context.Context, id, changeNote, actorID string) (Application, error) {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return Application{}, err
	}
	defer rollback(ctx, tx)
	var raw []byte
	if err = tx.QueryRow(ctx, `SELECT draft_config FROM gateway.applications WHERE id=$1 FOR UPDATE`, id).Scan(&raw); errors.Is(err, pgx.ErrNoRows) {
		return Application{}, ErrNotFound
	} else if err != nil {
		return Application{}, err
	}
	var config ApplicationConfig
	if err = json.Unmarshal(raw, &config); err != nil {
		return Application{}, errors.New("应用草稿配置无效")
	}
	if err = normalizeApplicationConfig(&config, true); err != nil {
		return Application{}, err
	}
	if err = s.validateApplicationRefs(ctx, config); err != nil {
		return Application{}, err
	}
	raw, _ = json.Marshal(config)
	var version int
	if err = tx.QueryRow(ctx, `SELECT coalesce(max(version),0)+1 FROM gateway.application_versions WHERE application_id=$1`, id).Scan(&version); err != nil {
		return Application{}, err
	}
	versionID, idErr := newUUID()
	if idErr != nil {
		return Application{}, idErr
	}
	if _, err = tx.Exec(ctx, `INSERT INTO gateway.application_versions(id,application_id,version,config,change_note,published_by) VALUES($1,$2,$3,$4,$5,$6)`, versionID, id, version, raw, strings.TrimSpace(changeNote), actorID); err != nil {
		return Application{}, err
	}
	if _, err = tx.Exec(ctx, `UPDATE gateway.applications SET published_version=$2,status='active',revision=revision+1,updated_at=clock_timestamp() WHERE id=$1`, id, version); err != nil {
		return Application{}, err
	}
	if err = emit(ctx, tx, "application.published", "application", id, actorID, map[string]any{"version": version}); err != nil {
		return Application{}, err
	}
	if err = tx.Commit(ctx); err != nil {
		return Application{}, err
	}
	return s.GetApplication(ctx, id)
}

func (s *Service) DeleteApplication(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.applications WHERE id=$1`, id)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrNotFound
	}
	if err = emit(ctx, tx, "application.deleted", "application", id, actorID, nil); err != nil {
		return err
	}
	return tx.Commit(ctx)
}

func (s *Service) ListApplicationVersions(ctx context.Context, id string) ([]map[string]any, error) {
	rows, err := s.pool.Query(ctx, `SELECT id::text,version,config,change_note,created_at FROM gateway.application_versions WHERE application_id=$1 ORDER BY version DESC`, id)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []map[string]any{}
	for rows.Next() {
		var versionID, note string
		var version int
		var raw []byte
		var created time.Time
		if err = rows.Scan(&versionID, &version, &raw, &note, &created); err != nil {
			return nil, err
		}
		var config ApplicationConfig
		_ = json.Unmarshal(raw, &config)
		items = append(items, map[string]any{"id": versionID, "version": version, "config": config, "change_note": note, "created_at": created})
	}
	return items, rows.Err()
}

func (s *Service) ListApplicationRuns(ctx context.Context, id string, limit int) ([]ApplicationRun, error) {
	if limit < 1 {
		limit = 50
	}
	if limit > 200 {
		limit = 200
	}
	rows, err := s.pool.Query(ctx, `SELECT id::text,application_id::text,request_id,status,error,version,latency_ms,retrieval_count,tool_count,created_at FROM gateway.application_runs WHERE application_id=$1 ORDER BY created_at DESC LIMIT $2`, id, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := []ApplicationRun{}
	for rows.Next() {
		var run ApplicationRun
		if err = rows.Scan(&run.ID, &run.ApplicationID, &run.RequestID, &run.Status, &run.Error, &run.Version, &run.LatencyMS, &run.RetrievalCount, &run.ToolCount, &run.CreatedAt); err != nil {
			return nil, err
		}
		items = append(items, run)
	}
	return items, rows.Err()
}

func (s *Service) RollbackApplication(ctx context.Context, id string, version int, changeNote, actorID string) (Application, error) {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return Application{}, err
	}
	defer rollback(ctx, tx)
	var raw []byte
	if err = tx.QueryRow(ctx, `SELECT config FROM gateway.application_versions WHERE application_id=$1 AND version=$2`, id, version).Scan(&raw); errors.Is(err, pgx.ErrNoRows) {
		return Application{}, ErrNotFound
	} else if err != nil {
		return Application{}, err
	}
	var config ApplicationConfig
	if err = json.Unmarshal(raw, &config); err != nil {
		return Application{}, errors.New("目标版本配置无效")
	}
	if err = normalizeApplicationConfig(&config, true); err != nil {
		return Application{}, err
	}
	if err = s.validateApplicationRefs(ctx, config); err != nil {
		return Application{}, err
	}
	var next int
	if err = tx.QueryRow(ctx, `SELECT coalesce(max(version),0)+1 FROM gateway.application_versions WHERE application_id=$1`, id).Scan(&next); err != nil {
		return Application{}, err
	}
	versionID, err := newUUID()
	if err != nil {
		return Application{}, err
	}
	note := strings.TrimSpace(changeNote)
	if note == "" {
		note = fmt.Sprintf("回滚到 v%d", version)
	}
	if _, err = tx.Exec(ctx, `INSERT INTO gateway.application_versions(id,application_id,version,config,change_note,published_by) VALUES($1,$2,$3,$4,$5,$6)`, versionID, id, next, raw, note, actorID); err != nil {
		return Application{}, err
	}
	if _, err = tx.Exec(ctx, `UPDATE gateway.applications SET draft_config=$2,published_version=$3,status='active',revision=revision+1,updated_at=clock_timestamp() WHERE id=$1`, id, raw, next); err != nil {
		return Application{}, err
	}
	if err = emit(ctx, tx, "application.rolled_back", "application", id, actorID, map[string]any{"source_version": version, "published_version": next}); err != nil {
		return Application{}, err
	}
	if err = tx.Commit(ctx); err != nil {
		return Application{}, err
	}
	return s.GetApplication(ctx, id)
}

func (s *Service) ApplicationDependencies(ctx context.Context, assetType, assetID string) ([]map[string]any, error) {
	apps, err := s.ListApplications(ctx)
	if err != nil {
		return nil, err
	}
	items := []map[string]any{}
	for _, app := range apps {
		stages := []struct {
			name   string
			config *ApplicationConfig
		}{{"draft", &app.DraftConfig}, {"published", app.PublishedConfig}}
		for _, stage := range stages {
			if stage.config == nil {
				continue
			}
			used := assetType == "prompt" && stage.config.PromptTemplateID == assetID
			if assetType == "knowledge" {
				for _, id := range stage.config.KnowledgeBaseIDs {
					if id == assetID {
						used = true
						break
					}
				}
			}
			if assetType == "tool" {
				for _, id := range stage.config.ToolIDs {
					if id == assetID {
						used = true
						break
					}
				}
			}
			if used {
				items = append(items, map[string]any{"application_id": app.ID, "code": app.Code, "name": app.Name, "stage": stage.name, "version": app.PublishedVersion})
			}
		}
	}
	return items, nil
}
