// Package domain contains pure domain models with no external dependencies.
package domain

import (
	"fmt"
	"maps"
	"net/url"
	"strconv"
	"strings"
	"time"
)

// BuildSpec defines what a build task should accomplish.
// It captures the user's intent and parameters for code generation.
type BuildSpec struct {
	// Template is the project template to use (e.g., "nextjs-landing").
	Template string `json:"template,omitempty"`

	// Prompt is the user's instruction for the build.
	Prompt string `json:"prompt"`

	// Variables contains template-specific substitution values.
	Variables map[string]string `json:"variables,omitempty"`

	// AutoCommit controls whether changes are automatically committed.
	AutoCommit bool `json:"auto_commit"`

	// AutoPush controls whether commits are automatically pushed.
	AutoPush bool `json:"auto_push"`

	// CallbackURL is the webhook URL for completion notification.
	CallbackURL string `json:"callback_url,omitempty"`

	// GitCloneURL is the HTTPS URL for cloning the project repository.
	// Required for builds that use AutoCommit/AutoPush on shared worker pods.
	GitCloneURL string `json:"git_clone_url,omitempty"`
}

// Validate checks that the BuildSpec has required fields.
func (s *BuildSpec) Validate() error {
	if s.Prompt == "" {
		return ErrPromptRequired
	}
	if s.CallbackURL != "" {
		if err := ValidateCallbackURL(s.CallbackURL); err != nil {
			return err
		}
	}
	return nil
}

// ValidateCallbackURL checks that a callback URL is safe to use.
// It rejects non-HTTPS URLs and private/internal network addresses.
func ValidateCallbackURL(rawURL string) error {
	u, err := url.Parse(rawURL)
	if err != nil {
		return fmt.Errorf("invalid callback URL: %w", err)
	}
	if u.Scheme != "https" {
		return fmt.Errorf("callback URL must use HTTPS scheme, got %q", u.Scheme)
	}
	if u.Host == "" {
		return fmt.Errorf("callback URL must have a host")
	}

	host := u.Hostname()
	lower := strings.ToLower(host)

	// Block localhost and loopback
	if lower == "localhost" || lower == "127.0.0.1" || lower == "::1" || lower == "[::1]" {
		return fmt.Errorf("callback URL must not point to localhost")
	}
	// Block common metadata endpoints
	if lower == "metadata.google.internal" || lower == "169.254.169.254" {
		return fmt.Errorf("callback URL must not point to cloud metadata service")
	}
	// Block private network ranges
	if strings.HasPrefix(lower, "10.") || strings.HasPrefix(lower, "192.168.") {
		return fmt.Errorf("callback URL must not point to private network addresses")
	}
	if strings.HasPrefix(lower, "172.") {
		// 172.16.0.0 - 172.31.255.255
		parts := strings.SplitN(lower, ".", 3)
		if len(parts) >= 2 {
			if octet, err := strconv.Atoi(parts[1]); err == nil && octet >= 16 && octet <= 31 {
				return fmt.Errorf("callback URL must not point to private network addresses")
			}
		}
	}
	return nil
}

// BuildResult captures the outcome of a build execution.
type BuildResult struct {
	// Success indicates whether the build completed successfully.
	Success bool `json:"success"`

	// Output is the agent's text output during the build.
	Output string `json:"output,omitempty"`

	// Error contains the error message if the build failed.
	Error string `json:"error,omitempty"`

	// ErrorCode categorizes the failure type for programmatic handling.
	// Values: RATE_LIMITED, AUTH_FAILED, TIMEOUT, STALE_WORKER, AGENT_ERROR, INVALID_SPEC
	ErrorCode WorkErrorCode `json:"error_code,omitempty"`

	// CommitSHA is the git commit hash if auto-commit was enabled.
	CommitSHA string `json:"commit_sha,omitempty"`

	// FilesChanged lists files modified during the build.
	FilesChanged []string `json:"files_changed,omitempty"`

	// DurationMs is the total execution time in milliseconds.
	DurationMs int64 `json:"duration_ms"`

	// Artifacts contains named outputs from the build (e.g., deploy URLs).
	Artifacts map[string]string `json:"artifacts,omitempty"`
}

// ToWorkResult converts a BuildResult to a WorkResult.
// Build-specific fields (commit_sha, files_changed, duration_ms) are
// promoted into the artifacts map, overwriting any existing keys with
// the same names.
func (r *BuildResult) ToWorkResult() *WorkResult {
	if r == nil {
		return &WorkResult{}
	}

	artifacts := make(map[string]string)
	maps.Copy(artifacts, r.Artifacts)

	// Promote build-specific fields into artifacts
	if r.CommitSHA != "" {
		artifacts["commit_sha"] = r.CommitSHA
	}
	if r.DurationMs > 0 {
		artifacts["duration_ms"] = strconv.FormatInt(r.DurationMs, 10)
	}
	if len(r.FilesChanged) > 0 {
		artifacts["files_changed_count"] = strconv.Itoa(len(r.FilesChanged))
	}

	output := r.Output
	if !r.Success && r.Error != "" {
		output = r.Error
	}

	return &WorkResult{
		Output:    output,
		Artifacts: artifacts,
	}
}

// BuildStatus represents the lifecycle state of a build.
type BuildStatus string

const (
	BuildStatusPending   BuildStatus = "pending"
	BuildStatusRunning   BuildStatus = "running"
	BuildStatusCompleted BuildStatus = "completed"
	BuildStatusFailed    BuildStatus = "failed"
	BuildStatusCancelled BuildStatus = "cancelled"
)

// IsValid returns true if the status is a known valid status.
func (s BuildStatus) IsValid() bool {
	switch s {
	case BuildStatusPending, BuildStatusRunning, BuildStatusCompleted,
		BuildStatusFailed, BuildStatusCancelled:
		return true
	}
	return false
}

// IsTerminal returns true if the build is in a final state.
func (s BuildStatus) IsTerminal() bool {
	switch s {
	case BuildStatusCompleted, BuildStatusFailed, BuildStatusCancelled:
		return true
	}
	return false
}

// BuildAuditEntry represents a single build's audit record.
type BuildAuditEntry struct {
	// TaskID is the work queue task ID.
	TaskID string `json:"task_id"`

	// ProjectID is the project this build belongs to.
	ProjectID string `json:"project_id"`

	// WorkerID is the worker that executed the build.
	WorkerID string `json:"worker_id,omitempty"`

	// Spec is the original build specification.
	Spec BuildSpec `json:"spec"`

	// Result is the build outcome (nil if not yet complete).
	Result *BuildResult `json:"result,omitempty"`

	// Status is the current build status.
	Status BuildStatus `json:"status"`

	// StartedAt is when the build was created/enqueued.
	StartedAt time.Time `json:"started_at"`

	// CompletedAt is when the build finished (nil if still running).
	CompletedAt *time.Time `json:"completed_at,omitempty"`
}
