package sqlite

import (
	"context"
	"database/sql"
	"encoding/json"
	"time"

	"chat-switchboard/models"
	"chat-switchboard/store"
)

// WorkflowStore implements store.WorkflowStore for SQLite.
type WorkflowStore struct{}

func NewWorkflowStore() *WorkflowStore { return &WorkflowStore{} }

// ── Workflow CRUD ───────────────────────────

func (s *WorkflowStore) Create(ctx context.Context, w *models.Workflow) error {
	w.ID = store.NewID()
	now := time.Now().UTC()
	w.CreatedAt = now
	w.UpdatedAt = now
	w.Version = 1
	branding := jsonOrEmpty(w.Branding)
	retention := jsonOrDefault(w.Retention, `{"mode":"archive"}`)
	_, err := DB.ExecContext(ctx, `
		INSERT INTO workflows (id, team_id, name, slug, description, branding, entry_mode,
		            is_active, version, on_complete, retention, webhook_url, webhook_secret,
		            created_by, created_at, updated_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		w.ID, w.TeamID, w.Name, w.Slug, w.Description, branding, w.EntryMode,
		boolToInt(w.IsActive), w.Version, jsonOrNull(w.OnComplete), retention,
		nullIfEmpty(w.WebhookURL), nullIfEmpty(w.WebhookSecret),
		w.CreatedBy, w.CreatedAt.Format(time.RFC3339), w.UpdatedAt.Format(time.RFC3339))
	return err
}

func (s *WorkflowStore) GetByID(ctx context.Context, id string) (*models.Workflow, error) {
	return s.scanOne(ctx, `
		SELECT id, team_id, name, slug, description, branding, entry_mode, is_active,
		       version, on_complete, retention, webhook_url, webhook_secret,
		       created_by, created_at, updated_at
		FROM workflows WHERE id = ?`, id)
}

func (s *WorkflowStore) GetBySlug(ctx context.Context, teamID *string, slug string) (*models.Workflow, error) {
	if teamID != nil {
		return s.scanOne(ctx, `
			SELECT id, team_id, name, slug, description, branding, entry_mode, is_active,
			       version, on_complete, retention, webhook_url, webhook_secret,
			       created_by, created_at, updated_at
			FROM workflows WHERE team_id = ? AND slug = ?`, *teamID, slug)
	}
	return s.scanOne(ctx, `
		SELECT id, team_id, name, slug, description, branding, entry_mode, is_active,
		       version, on_complete, retention, webhook_url, webhook_secret,
		       created_by, created_at, updated_at
		FROM workflows WHERE team_id IS NULL AND slug = ?`, slug)
}

func (s *WorkflowStore) Update(ctx context.Context, id string, patch models.WorkflowPatch) error {
	sets := []string{}
	args := []interface{}{}

	if patch.Name != nil {
		sets = append(sets, "name = ?")
		args = append(args, *patch.Name)
	}
	if patch.Description != nil {
		sets = append(sets, "description = ?")
		args = append(args, *patch.Description)
	}
	if patch.Branding != nil {
		sets = append(sets, "branding = ?")
		args = append(args, string(*patch.Branding))
	}
	if patch.EntryMode != nil {
		sets = append(sets, "entry_mode = ?")
		args = append(args, *patch.EntryMode)
	}
	if patch.IsActive != nil {
		sets = append(sets, "is_active = ?")
		args = append(args, boolToInt(*patch.IsActive))
	}
	if patch.OnComplete != nil {
		sets = append(sets, "on_complete = ?")
		args = append(args, string(*patch.OnComplete))
	}
	if patch.Retention != nil {
		sets = append(sets, "retention = ?")
		args = append(args, string(*patch.Retention))
	}
	if patch.WebhookURL != nil {
		sets = append(sets, "webhook_url = ?")
		args = append(args, *patch.WebhookURL)
	}
	if patch.WebhookSecret != nil {
		sets = append(sets, "webhook_secret = ?")
		args = append(args, *patch.WebhookSecret)
	}

	if len(sets) == 0 {
		return nil
	}

	sets = append(sets, "version = version + 1")
	sets = append(sets, "updated_at = ?")
	args = append(args, time.Now().UTC().Format(time.RFC3339))

	q := "UPDATE workflows SET "
	for i, s := range sets {
		if i > 0 {
			q += ", "
		}
		q += s
	}
	q += " WHERE id = ?"
	args = append(args, id)

	_, err := DB.ExecContext(ctx, q, args...)
	return err
}

func (s *WorkflowStore) Delete(ctx context.Context, id string) error {
	_, err := DB.ExecContext(ctx, `DELETE FROM workflows WHERE id = ?`, id)
	return err
}

func (s *WorkflowStore) ListForTeam(ctx context.Context, teamID string) ([]models.Workflow, error) {
	return s.queryWorkflows(ctx, `
		SELECT id, team_id, name, slug, description, branding, entry_mode, is_active,
		       version, on_complete, retention, webhook_url, webhook_secret,
		       created_by, created_at, updated_at
		FROM workflows WHERE team_id = ?
		ORDER BY name ASC`, teamID)
}

func (s *WorkflowStore) ListGlobal(ctx context.Context) ([]models.Workflow, error) {
	return s.queryWorkflows(ctx, `
		SELECT id, team_id, name, slug, description, branding, entry_mode, is_active,
		       version, on_complete, retention, webhook_url, webhook_secret,
		       created_by, created_at, updated_at
		FROM workflows WHERE team_id IS NULL
		ORDER BY name ASC`)
}

// ── Stages ──────────────────────────────────

func (s *WorkflowStore) CreateStage(ctx context.Context, st *models.WorkflowStage) error {
	st.ID = store.NewID()
	st.CreatedAt = time.Now().UTC()
	formTpl := jsonOrEmpty(st.FormTemplate)
	transRules := jsonOrEmpty(st.TransitionRules)
	stageMode := st.StageMode
	if stageMode == "" {
		stageMode = models.StageModeChatOnly
	}
	_, err := DB.ExecContext(ctx, `
		INSERT INTO workflow_stages (id, workflow_id, ordinal, name, persona_id, assignment_team_id,
		            form_template, stage_mode, history_mode, auto_transition, transition_rules,
		            surface_pkg_id, sla_seconds, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		st.ID, st.WorkflowID, st.Ordinal, st.Name, st.PersonaID, st.AssignmentTeamID,
		formTpl, stageMode, st.HistoryMode, boolToInt(st.AutoTransition), transRules,
		st.SurfacePkgID, st.SLASeconds, st.CreatedAt.Format(time.RFC3339))
	return err
}

func (s *WorkflowStore) ListStages(ctx context.Context, workflowID string) ([]models.WorkflowStage, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, workflow_id, ordinal, name, persona_id, assignment_team_id,
		       form_template, stage_mode, history_mode, auto_transition, transition_rules,
		       surface_pkg_id, sla_seconds, created_at
		FROM workflow_stages WHERE workflow_id = ?
		ORDER BY ordinal ASC`, workflowID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []models.WorkflowStage
	for rows.Next() {
		var stg models.WorkflowStage
		var formTpl, transRules string
		var autoTrans int
		if err := rows.Scan(&stg.ID, &stg.WorkflowID, &stg.Ordinal, &stg.Name,
			&stg.PersonaID, &stg.AssignmentTeamID, &formTpl, &stg.StageMode,
			&stg.HistoryMode, &autoTrans, &transRules,
			&stg.SurfacePkgID, &stg.SLASeconds, st(&stg.CreatedAt)); err != nil {
			return nil, err
		}
		stg.FormTemplate = json.RawMessage(formTpl)
		stg.TransitionRules = json.RawMessage(transRules)
		stg.AutoTransition = autoTrans != 0
		result = append(result, stg)
	}
	return result, rows.Err()
}

func (s *WorkflowStore) UpdateStage(ctx context.Context, st *models.WorkflowStage) error {
	formTpl := jsonOrEmpty(st.FormTemplate)
	transRules := jsonOrEmpty(st.TransitionRules)
	stageMode := st.StageMode
	if stageMode == "" {
		stageMode = models.StageModeChatOnly
	}
	_, err := DB.ExecContext(ctx, `
		UPDATE workflow_stages
		SET ordinal = ?, name = ?, persona_id = ?, assignment_team_id = ?,
		    form_template = ?, stage_mode = ?, history_mode = ?, auto_transition = ?,
		    transition_rules = ?, surface_pkg_id = ?, sla_seconds = ?
		WHERE id = ?`,
		st.Ordinal, st.Name, st.PersonaID, st.AssignmentTeamID,
		formTpl, stageMode, st.HistoryMode, boolToInt(st.AutoTransition), transRules,
		st.SurfacePkgID, st.SLASeconds, st.ID)
	return err
}

func (s *WorkflowStore) DeleteStage(ctx context.Context, id string) error {
	_, err := DB.ExecContext(ctx, `DELETE FROM workflow_stages WHERE id = ?`, id)
	return err
}

func (s *WorkflowStore) ReorderStages(ctx context.Context, workflowID string, orderedIDs []string) error {
	tx, err := DB.BeginTx(ctx, nil)
	if err != nil {
		return err
	}
	defer tx.Rollback()

	for i, id := range orderedIDs {
		if _, err := tx.ExecContext(ctx, `
			UPDATE workflow_stages SET ordinal = ? WHERE id = ? AND workflow_id = ?`,
			i, id, workflowID); err != nil {
			return err
		}
	}
	return tx.Commit()
}

// ── Versions ────────────────────────────────

func (s *WorkflowStore) Publish(ctx context.Context, v *models.WorkflowVersion) error {
	v.ID = store.NewID()
	v.CreatedAt = time.Now().UTC()
	_, err := DB.ExecContext(ctx, `
		INSERT INTO workflow_versions (id, workflow_id, version_number, snapshot, created_at)
		VALUES (?, ?, ?, ?, ?)`,
		v.ID, v.WorkflowID, v.VersionNumber, string(v.Snapshot),
		v.CreatedAt.Format(time.RFC3339))
	return err
}

func (s *WorkflowStore) GetVersion(ctx context.Context, workflowID string, versionNumber int) (*models.WorkflowVersion, error) {
	v := &models.WorkflowVersion{}
	var snapshot string
	err := DB.QueryRowContext(ctx, `
		SELECT id, workflow_id, version_number, snapshot, created_at
		FROM workflow_versions WHERE workflow_id = ? AND version_number = ?`,
		workflowID, versionNumber,
	).Scan(&v.ID, &v.WorkflowID, &v.VersionNumber, &snapshot, st(&v.CreatedAt))
	if err != nil {
		return nil, err
	}
	v.Snapshot = json.RawMessage(snapshot)
	return v, nil
}

func (s *WorkflowStore) GetLatestVersion(ctx context.Context, workflowID string) (*models.WorkflowVersion, error) {
	v := &models.WorkflowVersion{}
	var snapshot string
	err := DB.QueryRowContext(ctx, `
		SELECT id, workflow_id, version_number, snapshot, created_at
		FROM workflow_versions WHERE workflow_id = ?
		ORDER BY version_number DESC LIMIT 1`,
		workflowID,
	).Scan(&v.ID, &v.WorkflowID, &v.VersionNumber, &snapshot, st(&v.CreatedAt))
	if err != nil {
		return nil, err
	}
	v.Snapshot = json.RawMessage(snapshot)
	return v, nil
}

// ── Helpers ─────────────────────────────────

func (s *WorkflowStore) scanOne(ctx context.Context, q string, args ...interface{}) (*models.Workflow, error) {
	w := &models.Workflow{}
	var branding, retention string
	var onComplete sql.NullString
	var webhookURL, webhookSecret sql.NullString
	var isActive int
	err := DB.QueryRowContext(ctx, q, args...).Scan(
		&w.ID, &w.TeamID, &w.Name, &w.Slug, &w.Description, &branding,
		&w.EntryMode, &isActive, &w.Version, &onComplete, &retention,
		&webhookURL, &webhookSecret,
		&w.CreatedBy, st(&w.CreatedAt), st(&w.UpdatedAt))
	if err != nil {
		return nil, err
	}
	w.Branding = json.RawMessage(branding)
	w.Retention = json.RawMessage(retention)
	w.IsActive = isActive != 0
	if onComplete.Valid {
		w.OnComplete = json.RawMessage(onComplete.String)
	}
	if webhookURL.Valid {
		w.WebhookURL = webhookURL.String
	}
	if webhookSecret.Valid {
		w.WebhookSecret = webhookSecret.String
	}
	return w, nil
}

func (s *WorkflowStore) queryWorkflows(ctx context.Context, q string, args ...interface{}) ([]models.Workflow, error) {
	rows, err := DB.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var result []models.Workflow
	for rows.Next() {
		var w models.Workflow
		var branding, retention string
		var onComplete sql.NullString
		var webhookURL, webhookSecret sql.NullString
		var isActive int
		if err := rows.Scan(&w.ID, &w.TeamID, &w.Name, &w.Slug, &w.Description,
			&branding, &w.EntryMode, &isActive, &w.Version, &onComplete,
			&retention, &webhookURL, &webhookSecret,
			&w.CreatedBy, st(&w.CreatedAt), st(&w.UpdatedAt)); err != nil {
			return nil, err
		}
		w.Branding = json.RawMessage(branding)
		w.Retention = json.RawMessage(retention)
		w.IsActive = isActive != 0
		if onComplete.Valid {
			w.OnComplete = json.RawMessage(onComplete.String)
		}
		if webhookURL.Valid {
			w.WebhookURL = webhookURL.String
		}
		if webhookSecret.Valid {
			w.WebhookSecret = webhookSecret.String
		}
		result = append(result, w)
	}
	return result, rows.Err()
}

func jsonOrEmpty(b json.RawMessage) string {
	if len(b) == 0 {
		return "{}"
	}
	return string(b)
}

func jsonOrDefault(b json.RawMessage, def string) string {
	if len(b) == 0 {
		return def
	}
	return string(b)
}

func jsonOrNull(b json.RawMessage) interface{} {
	if len(b) == 0 || string(b) == "null" {
		return nil
	}
	return string(b)
}

func nullIfEmpty(s string) interface{} {
	if s == "" {
		return nil
	}
	return s
}

// ── Assignments (v0.29.0-cs3) ───────────────────────────────────────────

func (s *WorkflowStore) CreateAssignment(ctx context.Context, a *store.WorkflowAssignment) error {
	a.ID = store.NewID()
	_, err := DB.ExecContext(ctx, `
		INSERT INTO workflow_assignments (id, channel_id, stage, team_id)
		VALUES (?, ?, ?, ?)
	`, a.ID, a.ChannelID, a.Stage, a.TeamID)
	return err
}

func (s *WorkflowStore) ListAssignmentsForTeam(ctx context.Context, teamID, status string) ([]store.WorkflowAssignment, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT id, channel_id, stage, team_id, assigned_to, status,
		       created_at, claimed_at, completed_at
		FROM workflow_assignments
		WHERE team_id = ? AND status = ?
		ORDER BY created_at DESC
	`, teamID, status)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanAssignments(rows)
}

func (s *WorkflowStore) ListAssignmentsMine(ctx context.Context, userID string) ([]store.WorkflowAssignment, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT DISTINCT wa.id, wa.channel_id, wa.stage, wa.team_id, wa.assigned_to, wa.status,
		       wa.created_at, wa.claimed_at, wa.completed_at
		FROM workflow_assignments wa
		LEFT JOIN team_members tm ON tm.team_id = wa.team_id AND tm.user_id = ?
		WHERE (wa.assigned_to = ? AND wa.status = 'claimed')
		   OR (wa.status = 'unassigned' AND tm.user_id IS NOT NULL)
		ORDER BY wa.created_at DESC
	`, userID, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanAssignments(rows)
}

func (s *WorkflowStore) ClaimAssignment(ctx context.Context, assignmentID, userID string) (int64, error) {
	res, err := DB.ExecContext(ctx, `
		UPDATE workflow_assignments
		SET assigned_to = ?, status = 'claimed', claimed_at = ?
		WHERE id = ? AND status = 'unassigned'
	`, userID, time.Now().UTC().Format(timeFmt), assignmentID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (s *WorkflowStore) CompleteAssignment(ctx context.Context, assignmentID string) (int64, error) {
	res, err := DB.ExecContext(ctx, `
		UPDATE workflow_assignments
		SET status = 'completed', completed_at = ?
		WHERE id = ? AND status = 'claimed'
	`, time.Now().UTC().Format(timeFmt), assignmentID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (s *WorkflowStore) GetAssignmentChannelID(ctx context.Context, assignmentID string) (string, error) {
	var channelID string
	err := DB.QueryRowContext(ctx,
		`SELECT channel_id FROM workflow_assignments WHERE id = ?`, assignmentID).Scan(&channelID)
	return channelID, err
}

// ── Lifecycle operations (v0.37.15) ──

func (s *WorkflowStore) CancelAssignmentsForChannel(ctx context.Context, channelID string) (int64, error) {
	res, err := DB.ExecContext(ctx, `
		UPDATE workflow_assignments
		SET status = 'cancelled'
		WHERE channel_id = ? AND status IN ('unassigned', 'claimed')
	`, channelID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (s *WorkflowStore) UnclaimAssignment(ctx context.Context, assignmentID string) (int64, error) {
	res, err := DB.ExecContext(ctx, `
		UPDATE workflow_assignments
		SET status = 'unassigned', assigned_to = NULL, claimed_at = NULL
		WHERE id = ? AND status = 'claimed'
	`, assignmentID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (s *WorkflowStore) ReassignAssignment(ctx context.Context, assignmentID, newUserID string) (int64, error) {
	res, err := DB.ExecContext(ctx, `
		UPDATE workflow_assignments
		SET assigned_to = ?, claimed_at = ?
		WHERE id = ? AND status = 'claimed'
	`, newUserID, time.Now().UTC().Format(timeFmt), assignmentID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (s *WorkflowStore) CancelAssignment(ctx context.Context, assignmentID string) (int64, error) {
	res, err := DB.ExecContext(ctx, `
		UPDATE workflow_assignments
		SET status = 'cancelled'
		WHERE id = ? AND status IN ('unassigned', 'claimed')
	`, assignmentID)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (s *WorkflowStore) TryRoundRobin(ctx context.Context, teamID, assignmentID string) (string, error) {
	rows, err := DB.QueryContext(ctx, `
		SELECT m.user_id, COALESCE(MAX(wa.claimed_at), '1970-01-01 00:00:00') as last_claim
		FROM team_members m
		LEFT JOIN workflow_assignments wa ON wa.assigned_to = m.user_id AND wa.team_id = ?
		WHERE m.team_id = ?
		GROUP BY m.user_id
		ORDER BY last_claim ASC
		LIMIT 1
	`, teamID, teamID)
	if err != nil {
		return "", err
	}
	defer rows.Close()

	if !rows.Next() {
		return "", nil
	}
	var userID, lastClaim string
	if err := rows.Scan(&userID, &lastClaim); err != nil {
		return "", err
	}

	_, err = DB.ExecContext(ctx, `
		UPDATE workflow_assignments
		SET assigned_to = ?, status = 'claimed', claimed_at = ?
		WHERE id = ? AND status = 'unassigned'
	`, userID, time.Now().UTC().Format(timeFmt), assignmentID)
	if err != nil {
		return "", err
	}
	return userID, nil
}

func scanAssignments(rows *sql.Rows) ([]store.WorkflowAssignment, error) {
	var result []store.WorkflowAssignment
	for rows.Next() {
		var a store.WorkflowAssignment
		var claimedAt, completedAt *time.Time
		if err := rows.Scan(&a.ID, &a.ChannelID, &a.Stage, &a.TeamID,
			&a.AssignedTo, &a.Status, st(&a.CreatedAt), stN(&claimedAt), stN(&completedAt)); err != nil {
			return nil, err
		}
		a.ClaimedAt = claimedAt
		a.CompletedAt = completedAt
		result = append(result, a)
	}
	if result == nil {
		result = []store.WorkflowAssignment{}
	}
	return result, rows.Err()
}

// ── Review Comments (v0.35.0) ───────────────────────────

func (s *WorkflowStore) GetAssignmentByID(ctx context.Context, id string) (*store.WorkflowAssignment, error) {
	var a store.WorkflowAssignment
	var rc string
	var claimedAt, completedAt *time.Time
	err := DB.QueryRowContext(ctx, `
		SELECT id, channel_id, stage, team_id, assigned_to, status,
		       COALESCE(review_comments, '[]'), created_at, claimed_at, completed_at
		FROM workflow_assignments WHERE id = ?
	`, id).Scan(&a.ID, &a.ChannelID, &a.Stage, &a.TeamID, &a.AssignedTo, &a.Status,
		&rc, st(&a.CreatedAt), stN(&claimedAt), stN(&completedAt))
	if err != nil {
		return nil, err
	}
	a.ReviewComments = json.RawMessage(rc)
	a.ClaimedAt = claimedAt
	a.CompletedAt = completedAt
	return &a, nil
}

func (s *WorkflowStore) AddReviewComment(ctx context.Context, assignmentID string, comment store.ReviewComment) error {
	commentJSON, err := json.Marshal(comment)
	if err != nil {
		return err
	}

	// SQLite: read, append, write back
	var existing string
	err = DB.QueryRowContext(ctx, `SELECT COALESCE(review_comments, '[]') FROM workflow_assignments WHERE id = ?`, assignmentID).Scan(&existing)
	if err != nil {
		return err
	}
	var arr []json.RawMessage
	_ = json.Unmarshal([]byte(existing), &arr)
	arr = append(arr, json.RawMessage(commentJSON))
	updated, _ := json.Marshal(arr)
	_, err = DB.ExecContext(ctx, `UPDATE workflow_assignments SET review_comments = ? WHERE id = ?`, string(updated), assignmentID)
	return err
}
