package domain

import (
	"net"
	"time"
)

// APIKeyID is a strongly-typed identifier for API keys.
type APIKeyID string

// Scope represents a permission scope for API keys.
type Scope string

const (
	ScopeProjectsRead    Scope = "projects:read"
	ScopeProjectsExecute Scope = "projects:execute"
	ScopeKeysRead        Scope = "keys:read"
	ScopeKeysWrite       Scope = "keys:write"
	ScopeAuditRead       Scope = "audit:read"
	ScopeQueueRead       Scope = "queue:read"
	ScopeQueueWrite      Scope = "queue:write"
	ScopeWebhookRead     Scope = "webhook:read"
	ScopeWebhookWrite    Scope = "webhook:write"
	ScopeWorkersRead     Scope = "workers:read"
	ScopeWorkersWrite    Scope = "workers:write"
	ScopeBuildRead       Scope = "build:read"
	ScopeBuildWrite      Scope = "build:write"
	ScopeVerifyRead      Scope = "verify:read"
	ScopeVerifyWrite     Scope = "verify:write"
	ScopeSessionsRead    Scope = "sessions:read"
	ScopeSessionsExecute Scope = "sessions:execute"
	ScopeAdmin           Scope = "admin"
)

// AllScopes is the list of all valid scopes.
var AllScopes = []Scope{
	ScopeProjectsRead,
	ScopeProjectsExecute,
	ScopeKeysRead,
	ScopeKeysWrite,
	ScopeAuditRead,
	ScopeQueueRead,
	ScopeQueueWrite,
	ScopeWebhookRead,
	ScopeWebhookWrite,
	ScopeWorkersRead,
	ScopeWorkersWrite,
	ScopeBuildRead,
	ScopeBuildWrite,
	ScopeVerifyRead,
	ScopeVerifyWrite,
	ScopeSessionsRead,
	ScopeSessionsExecute,
	ScopeAdmin,
}

// ScopeDescriptions provides human-readable descriptions.
var ScopeDescriptions = map[Scope]string{
	ScopeProjectsRead:    "List and view project details",
	ScopeProjectsExecute: "Execute commands (claude, shell, git) on projects",
	ScopeKeysRead:        "List API keys (metadata only, not secrets)",
	ScopeKeysWrite:       "Create and revoke API keys",
	ScopeAuditRead:       "View audit logs for command executions",
	ScopeQueueRead:       "View queued commands and queue status",
	ScopeQueueWrite:      "Enqueue and cancel queued commands",
	ScopeWebhookRead:     "View webhooks and delivery history",
	ScopeWebhookWrite:    "Create, update, and delete webhooks",
	ScopeWorkersRead:     "View workers and worker status",
	ScopeWorkersWrite:    "Manage workers (drain, register)",
	ScopeBuildRead:       "View build status and history",
	ScopeBuildWrite:      "Start and manage builds",
	ScopeVerifyRead:      "View verify tasks and capture results",
	ScopeVerifyWrite:     "Submit and cancel verify tasks",
	ScopeSessionsRead:    "View interactive development sessions",
	ScopeSessionsExecute: "Create and end interactive development sessions",
	ScopeAdmin:           "Full administrative access (includes all scopes)",
}

// IsValid checks if a scope is valid.
func (s Scope) IsValid() bool {
	for _, scope := range AllScopes {
		if scope == s {
			return true
		}
	}
	return false
}

// String returns the scope as a string.
func (s Scope) String() string {
	return string(s)
}

// ScopesFromStrings converts string slice to Scope slice.
func ScopesFromStrings(ss []string) []Scope {
	scopes := make([]Scope, len(ss))
	for i, s := range ss {
		scopes[i] = Scope(s)
	}
	return scopes
}

// ScopesToStrings converts Scope slice to string slice.
func ScopesToStrings(scopes []Scope) []string {
	ss := make([]string, len(scopes))
	for i, s := range scopes {
		ss[i] = string(s)
	}
	return ss
}

// ValidateScopes checks if all scopes are valid.
func ValidateScopes(scopes []Scope) bool {
	for _, s := range scopes {
		if !s.IsValid() {
			return false
		}
	}
	return true
}

// HasScope checks if a scope list contains a required scope.
// Admin scope grants access to everything.
func HasScope(scopes []Scope, required Scope) bool {
	for _, s := range scopes {
		if s == ScopeAdmin || s == required {
			return true
		}
	}
	return false
}

// HasAnyScope checks if a scope list contains any of the required scopes.
func HasAnyScope(scopes []Scope, required ...Scope) bool {
	for _, r := range required {
		if HasScope(scopes, r) {
			return true
		}
	}
	return false
}

// APIKey represents an API key for authentication.
type APIKey struct {
	ID         APIKeyID
	Name       string
	KeyPrefix  string // First 8 chars of key for identification
	Scopes     []Scope
	ProjectIDs []ProjectID // nil = access to all projects
	AllowedIPs []string    // CIDR notation, e.g., ["192.168.1.0/24", "10.0.0.0/8"]; nil = no restriction
	CreatedAt  time.Time
	ExpiresAt  *time.Time
	LastUsedAt *time.Time
	RevokedAt  *time.Time
	CreatedBy  string
}

// IsExpired returns true if the key has expired.
func (k *APIKey) IsExpired() bool {
	if k.ExpiresAt == nil {
		return false
	}
	return time.Now().After(*k.ExpiresAt)
}

// IsRevoked returns true if the key has been revoked.
func (k *APIKey) IsRevoked() bool {
	return k.RevokedAt != nil
}

// IsActive returns true if the key is valid for use.
func (k *APIKey) IsActive() bool {
	return !k.IsRevoked() && !k.IsExpired()
}

// HasScope returns true if the key has the specified scope.
func (k *APIKey) HasScope(scope Scope) bool {
	// Admin scope grants all permissions
	for _, s := range k.Scopes {
		if s == ScopeAdmin || s == scope {
			return true
		}
	}
	return false
}

// HasAnyScope returns true if the key has any of the specified scopes.
func (k *APIKey) HasAnyScope(scopes ...Scope) bool {
	for _, scope := range scopes {
		if k.HasScope(scope) {
			return true
		}
	}
	return false
}

// HasProjectAccess returns true if the key can access the given project.
func (k *APIKey) HasProjectAccess(projectID ProjectID) bool {
	// Admin or nil project list means access to all projects
	if k.HasScope(ScopeAdmin) || k.ProjectIDs == nil {
		return true
	}
	for _, pid := range k.ProjectIDs {
		if pid == projectID {
			return true
		}
	}
	return false
}

// IsIPAllowed checks if the given IP address is allowed by the key's IP restrictions.
// Returns true if no IP restrictions are set or if the IP matches any allowed CIDR.
func (k *APIKey) IsIPAllowed(clientIP string) bool {
	// No restrictions means all IPs are allowed
	if len(k.AllowedIPs) == 0 {
		return true
	}

	ip := net.ParseIP(clientIP)
	if ip == nil {
		return false
	}

	for _, cidr := range k.AllowedIPs {
		_, network, err := net.ParseCIDR(cidr)
		if err != nil {
			// If not a CIDR, try parsing as single IP
			allowedIP := net.ParseIP(cidr)
			if allowedIP != nil && allowedIP.Equal(ip) {
				return true
			}
			continue
		}
		if network.Contains(ip) {
			return true
		}
	}
	return false
}
