package store

import (
	"database/sql"
	"time"
)

// Template is a reusable sandbox configuration. Cloning a template into
// a sandbox materializes a sandbox row + sandbox_services from the
// template rows.
type Template struct {
	ID            string            `json:"id"`
	Name          string            `json:"name"`
	GatewayBranch string            `json:"gatewayBranch,omitempty"`
	CreatedAt     int64             `json:"createdAt"`
	UpdatedAt     int64             `json:"updatedAt"`
	Services      []TemplateService `json:"services"`
}

// TemplateService is one microservice entry in a template.
type TemplateService struct {
	ID         string `json:"id"`
	TemplateID string `json:"templateId"`
	Repo       string `json:"repo"`
	Branch     string `json:"branch,omitempty"`
	EnvID      string `json:"envId,omitempty"`
	HostPort   int    `json:"hostPort,omitempty"`
	UseOCP     bool   `json:"useOcp"`
}

// CreateTemplate persists a template and its services. Caller assigns IDs.
func (s *Store) CreateTemplate(t Template) error {
	now := time.Now().UnixMilli()
	if t.CreatedAt == 0 {
		t.CreatedAt = now
	}
	t.UpdatedAt = now
	tx, err := s.db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if _, err := tx.Exec(
		`INSERT INTO templates(id, name, gateway_branch, created_at, updated_at)
		 VALUES(?,?,?,?,?)`,
		t.ID, t.Name, t.GatewayBranch, t.CreatedAt, t.UpdatedAt,
	); err != nil {
		return err
	}
	for i := range t.Services {
		svc := &t.Services[i]
		if svc.ID == "" {
			svc.ID = t.ID + "-" + svc.Repo
			svc.TemplateID = t.ID
		}
		if err := insertTemplateService(tx, *svc); err != nil {
			return err
		}
	}
	return tx.Commit()
}

// UpdateTemplate replaces the template's mutable fields. Services are
// replaced wholesale.
func (s *Store) UpdateTemplate(t Template) error {
	now := time.Now().UnixMilli()
	t.UpdatedAt = now
	tx, err := s.db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	res, err := tx.Exec(
		`UPDATE templates SET name=?, gateway_branch=?, updated_at=? WHERE id=?`,
		t.Name, t.GatewayBranch, t.UpdatedAt, t.ID)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return ErrNotFound
	}
	if _, err := tx.Exec(`DELETE FROM template_services WHERE template_id=?`, t.ID); err != nil {
		return err
	}
	for i := range t.Services {
		svc := &t.Services[i]
		svc.TemplateID = t.ID
		if svc.ID == "" {
			svc.ID = t.ID + "-" + svc.Repo
		}
		if err := insertTemplateService(tx, *svc); err != nil {
			return err
		}
	}
	return tx.Commit()
}

// GetTemplate returns one template with its services. ErrNotFound if missing.
func (s *Store) GetTemplate(id string) (*Template, error) {
	row := s.db.QueryRow(
		`SELECT id, name, COALESCE(gateway_branch,''), created_at, updated_at
		 FROM templates WHERE id=?`, id)
	var t Template
	err := row.Scan(&t.ID, &t.Name, &t.GatewayBranch, &t.CreatedAt, &t.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, err
	}
	if err := s.loadTemplateServices(&t); err != nil {
		return nil, err
	}
	return &t, nil
}

// ListTemplates returns all templates, newest first.
func (s *Store) ListTemplates() ([]Template, error) {
	rows, err := s.db.Query(
		`SELECT id, name, COALESCE(gateway_branch,''), created_at, updated_at
		 FROM templates ORDER BY created_at DESC`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []Template
	for rows.Next() {
		var t Template
		if err := rows.Scan(&t.ID, &t.Name, &t.GatewayBranch, &t.CreatedAt, &t.UpdatedAt); err != nil {
			return nil, err
		}
		out = append(out, t)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	for i := range out {
		if err := s.loadTemplateServices(&out[i]); err != nil {
			return nil, err
		}
	}
	return out, nil
}

// DeleteTemplate removes the template and its services. ErrNotFound if missing.
func (s *Store) DeleteTemplate(id string) error {
	tx, err := s.db.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()
	if _, err := tx.Exec(`DELETE FROM template_services WHERE template_id=?`, id); err != nil {
		return err
	}
	res, err := tx.Exec(`DELETE FROM templates WHERE id=?`, id)
	if err != nil {
		return err
	}
	n, _ := res.RowsAffected()
	if n == 0 {
		return ErrNotFound
	}
	return tx.Commit()
}

// CloneTemplateIntoSandbox materializes a sandbox from a template. The
// caller assigns the new sandbox id and name. The gateway_branch and
// each service are copied; hostPort is preserved as-is (the caller may
// then update it to claim a fresh port via SetSandboxServicePort).
func (s *Store) CloneTemplateIntoSandbox(templateID string, sb Sandbox) error {
	t, err := s.GetTemplate(templateID)
	if err != nil {
		return err
	}
	sb.GatewayBranch = t.GatewayBranch
	sb.Services = make([]SandboxService, 0, len(t.Services))
	for _, ts := range t.Services {
		sb.Services = append(sb.Services, SandboxService{
			SandboxID: sb.ID,
			Repo:      ts.Repo,
			Branch:    ts.Branch,
			EnvID:     ts.EnvID,
			HostPort:  ts.HostPort,
			UseOCP:    ts.UseOCP,
		})
	}
	return s.CreateSandbox(sb)
}

func (s *Store) loadTemplateServices(t *Template) error {
	rows, err := s.db.Query(
		`SELECT id, template_id, repo, COALESCE(branch,''), COALESCE(env_id,''),
		        COALESCE(host_port,0), COALESCE(use_ocp,0)
		 FROM template_services WHERE template_id=? ORDER BY repo`, t.ID)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		var svc TemplateService
		var useOCP int
		if err := rows.Scan(&svc.ID, &svc.TemplateID, &svc.Repo, &svc.Branch,
			&svc.EnvID, &svc.HostPort, &useOCP); err != nil {
			return err
		}
		svc.UseOCP = useOCP == 1
		t.Services = append(t.Services, svc)
	}
	return rows.Err()
}

func insertTemplateService(tx *sql.Tx, svc TemplateService) error {
	_, err := tx.Exec(
		`INSERT INTO template_services(id, template_id, repo, branch, env_id, host_port, use_ocp)
		 VALUES(?,?,?,?,?,?,?)`,
		svc.ID, svc.TemplateID, svc.Repo, svc.Branch, nullString(svc.EnvID),
		nullInt(svc.HostPort), boolInt(svc.UseOCP),
	)
	return err
}

