package store

import (
	"database/sql"
	"errors"
	"fmt"
	"strings"

	"github.com/google/uuid"
)

// CreateDeploy inserts a new deploy record.
func (s *Store) CreateDeploy(d Deploy) (Deploy, error) {
	d.ID = uuid.New().String()
	d.StartedAt = Now()
	if d.Status == "" {
		d.Status = "pending"
	}

	_, err := s.db.Exec(
		`INSERT INTO deploys (id, project_id, stage_id, instance_id, image_tag, status, started_at, finished_at, error)
		 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		d.ID, d.ProjectID, d.StageID, d.InstanceID, d.ImageTag, d.Status, d.StartedAt, d.FinishedAt, d.Error,
	)
	if err != nil {
		return Deploy{}, fmt.Errorf("insert deploy: %w", err)
	}
	return d, nil
}

// GetDeployByID returns a single deploy by its ID.
func (s *Store) GetDeployByID(id string) (Deploy, error) {
	var d Deploy
	err := s.db.QueryRow(
		`SELECT id, project_id, stage_id, instance_id, image_tag, status, started_at, finished_at, error
		 FROM deploys WHERE id = ?`, id,
	).Scan(&d.ID, &d.ProjectID, &d.StageID, &d.InstanceID, &d.ImageTag, &d.Status, &d.StartedAt, &d.FinishedAt, &d.Error)
	if errors.Is(err, sql.ErrNoRows) {
		return Deploy{}, fmt.Errorf("deploy %s: %w", id, ErrNotFound)
	}
	if err != nil {
		return Deploy{}, fmt.Errorf("query deploy: %w", err)
	}
	return d, nil
}

// GetDeploysByProjectID returns all deploys for a project, newest first.
func (s *Store) GetDeploysByProjectID(projectID string) ([]Deploy, error) {
	rows, err := s.db.Query(
		`SELECT id, project_id, stage_id, instance_id, image_tag, status, started_at, finished_at, error
		 FROM deploys WHERE project_id = ? ORDER BY started_at DESC`, projectID,
	)
	if err != nil {
		return nil, fmt.Errorf("query deploys: %w", err)
	}
	defer rows.Close()

	return scanDeploys(rows)
}

// GetRecentDeploys returns the most recent deploys across all projects.
func (s *Store) GetRecentDeploys(limit int) ([]Deploy, error) {
	rows, err := s.db.Query(
		`SELECT id, project_id, stage_id, instance_id, image_tag, status, started_at, finished_at, error
		 FROM deploys ORDER BY started_at DESC LIMIT ?`, limit,
	)
	if err != nil {
		return nil, fmt.Errorf("query recent deploys: %w", err)
	}
	defer rows.Close()

	return scanDeploys(rows)
}

// UpdateDeployStatus sets the status (and optionally error and finished_at) on a deploy.
func (s *Store) UpdateDeployStatus(id string, status string, deployErr string) error {
	ts := Now()
	var finishedAt string
	if IsTerminalDeployStatus(status) {
		finishedAt = ts
	}

	result, err := s.db.Exec(
		`UPDATE deploys SET status=?, error=?, finished_at=? WHERE id=?`,
		status, deployErr, finishedAt, id,
	)
	if err != nil {
		return fmt.Errorf("update deploy status: %w", err)
	}
	n, _ := result.RowsAffected()
	if n == 0 {
		return fmt.Errorf("deploy %s: %w", id, ErrNotFound)
	}
	return nil
}

// SetDeployInstanceID links a deploy to the instance it created.
func (s *Store) SetDeployInstanceID(deployID string, instanceID string) error {
	result, err := s.db.Exec(`UPDATE deploys SET instance_id=? WHERE id=?`, instanceID, deployID)
	if err != nil {
		return fmt.Errorf("set deploy instance: %w", err)
	}
	n, _ := result.RowsAffected()
	if n == 0 {
		return fmt.Errorf("deploy %s: %w", deployID, ErrNotFound)
	}
	return nil
}

// AppendDeployLog adds a log entry for a deploy.
func (s *Store) AppendDeployLog(deployID string, message string, level string) error {
	if level == "" {
		level = "info"
	}
	_, err := s.db.Exec(
		`INSERT INTO deploy_logs (deploy_id, message, level, created_at) VALUES (?, ?, ?, ?)`,
		deployID, message, level, Now(),
	)
	if err != nil {
		return fmt.Errorf("append deploy log: %w", err)
	}
	return nil
}

// GetDeployLogs returns all log entries for a deploy, ordered chronologically.
func (s *Store) GetDeployLogs(deployID string) ([]DeployLog, error) {
	rows, err := s.db.Query(
		`SELECT id, deploy_id, message, level, created_at
		 FROM deploy_logs WHERE deploy_id = ? ORDER BY id`, deployID,
	)
	if err != nil {
		return nil, fmt.Errorf("query deploy logs: %w", err)
	}
	defer rows.Close()

	logs := []DeployLog{}
	for rows.Next() {
		var l DeployLog
		if err := rows.Scan(&l.ID, &l.DeployID, &l.Message, &l.Level, &l.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan deploy log: %w", err)
		}
		logs = append(logs, l)
	}
	return logs, rows.Err()
}

// scanDeploys is a helper that scans deploy rows from a cursor.
func scanDeploys(rows *sql.Rows) ([]Deploy, error) {
	deploys := []Deploy{}
	for rows.Next() {
		var d Deploy
		if err := rows.Scan(&d.ID, &d.ProjectID, &d.StageID, &d.InstanceID, &d.ImageTag, &d.Status, &d.StartedAt, &d.FinishedAt, &d.Error); err != nil {
			return nil, fmt.Errorf("scan deploy: %w", err)
		}
		deploys = append(deploys, d)
	}
	return deploys, rows.Err()
}

// IsTerminalDeployStatus returns true if the status indicates the deploy is finished.
func IsTerminalDeployStatus(status string) bool {
	switch status {
	case "success", "failed", "rolled_back":
		return true
	default:
		return false
	}
}

// GetDeploys returns deploys with optional filtering by project and stage, with pagination.
func (s *Store) GetDeploys(projectID, stageID string, limit, offset int) ([]Deploy, error) {
	query := `SELECT id, project_id, stage_id, instance_id, image_tag, status, started_at, finished_at, error FROM deploys`
	var args []any
	var conditions []string

	if projectID != "" {
		conditions = append(conditions, "project_id = ?")
		args = append(args, projectID)
	}
	if stageID != "" {
		conditions = append(conditions, "stage_id = ?")
		args = append(args, stageID)
	}

	if len(conditions) > 0 {
		query += " WHERE " + strings.Join(conditions, " AND ")
	}
	query += " ORDER BY started_at DESC LIMIT ? OFFSET ?"
	args = append(args, limit, offset)

	rows, err := s.db.Query(query, args...)
	if err != nil {
		return nil, fmt.Errorf("query deploys: %w", err)
	}
	defer rows.Close()

	return scanDeploys(rows)
}

// CleanupOldDeploys removes deploy records and their logs older than the given
// number of days. Returns the number of deploys removed.
func (s *Store) CleanupOldDeploys(retentionDays int) (int64, error) {
	cutoff := fmt.Sprintf("-%d days", retentionDays)
	result, err := s.db.Exec(
		`DELETE FROM deploys WHERE started_at < datetime('now', ?)`, cutoff,
	)
	if err != nil {
		return 0, fmt.Errorf("cleanup old deploys: %w", err)
	}
	n, _ := result.RowsAffected()
	return n, nil
}
