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

import (
	"crypto/rand"
	"fmt"
	"regexp"
	"strings"
	"time"
)

// DomainType represents the type of domain association with a project.
type DomainType string

const (
	// DomainTypePrimaryAuto is a system-generated random subdomain (e.g., k7m2x9p4.threesix.ai).
	DomainTypePrimaryAuto DomainType = "primary_auto"

	// DomainTypePrimaryCustom is a user-chosen subdomain (e.g., my-app.threesix.ai).
	DomainTypePrimaryCustom DomainType = "primary_custom"

	// DomainTypeAlias is an additional domain pointing to the project (e.g., www.myapp.com).
	DomainTypeAlias DomainType = "alias"
)

// Valid returns true if the domain type is recognized.
func (t DomainType) Valid() bool {
	switch t {
	case DomainTypePrimaryAuto, DomainTypePrimaryCustom, DomainTypeAlias:
		return true
	}
	return false
}

// IsPrimary returns true if this is a primary domain (auto or custom).
func (t DomainType) IsPrimary() bool {
	return t == DomainTypePrimaryAuto || t == DomainTypePrimaryCustom
}

// ProjectDomain represents a domain associated with a project.
type ProjectDomain struct {
	ID            int64
	ProjectID     string
	Domain        string
	Type          DomainType
	DNSRecordID   string // Cloudflare record ID for cleanup
	DNSRecordType string // A, CNAME, etc.
	Verified      bool
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

// Slug generation constants.
const (
	// SlugLength is the length of generated slugs.
	SlugLength = 8

	// slugChars are the allowed characters for slugs.
	// Excludes ambiguous chars: 0/o, 1/l/i to prevent confusion.
	slugChars = "23456789abcdefghjkmnpqrstuvwxyz"
)

// slugCharRegex validates that a slug contains only allowed characters.
var slugCharRegex = regexp.MustCompile(`^[23456789abcdefghjkmnpqrstuvwxyz]+$`)

// GenerateSlug creates a random 8-character slug for project identification.
// Uses a restricted character set that excludes ambiguous characters.
func GenerateSlug() (string, error) {
	bytes := make([]byte, SlugLength)
	if _, err := rand.Read(bytes); err != nil {
		return "", fmt.Errorf("failed to generate random bytes: %w", err)
	}

	result := make([]byte, SlugLength)
	for i := range SlugLength {
		result[i] = slugChars[int(bytes[i])%len(slugChars)]
	}
	return string(result), nil
}

// ValidateSlug checks if a slug is valid (correct length and characters).
func ValidateSlug(slug string) error {
	if len(slug) != SlugLength {
		return fmt.Errorf("slug must be exactly %d characters", SlugLength)
	}
	if !slugCharRegex.MatchString(slug) {
		return fmt.Errorf("slug contains invalid characters")
	}
	return nil
}

// Domain validation patterns.
var (
	// subdomainRegex validates subdomains under a base domain.
	// Lowercase letters, digits, and hyphens. Must start with a letter.
	subdomainRegex = regexp.MustCompile(`^[a-z][a-z0-9-]*$`)

	// fqdnRegex validates fully qualified domain names.
	fqdnRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$`)
)

// ValidateSubdomain validates a subdomain name (without the base domain).
func ValidateSubdomain(subdomain string) error {
	if subdomain == "" {
		return fmt.Errorf("subdomain cannot be empty")
	}
	if len(subdomain) > 63 {
		return fmt.Errorf("subdomain cannot exceed 63 characters")
	}
	if !subdomainRegex.MatchString(subdomain) {
		return fmt.Errorf("subdomain must be lowercase alphanumeric with hyphens, starting with a letter")
	}
	if reservedProjectNames[subdomain] {
		return fmt.Errorf("subdomain %q is reserved", subdomain)
	}
	return nil
}

// ValidateFQDN validates a fully qualified domain name.
func ValidateFQDN(domain string) error {
	if domain == "" {
		return fmt.Errorf("domain cannot be empty")
	}
	if len(domain) > 253 {
		return fmt.Errorf("domain cannot exceed 253 characters")
	}
	domain = strings.ToLower(domain)
	if !fqdnRegex.MatchString(domain) {
		return fmt.Errorf("invalid domain format")
	}
	return nil
}

// IsSubdomainOf checks if domain is a subdomain of baseDomain.
func IsSubdomainOf(domain, baseDomain string) bool {
	domain = strings.ToLower(domain)
	baseDomain = strings.ToLower(baseDomain)
	suffix := "." + baseDomain
	return strings.HasSuffix(domain, suffix)
}

// ExtractSubdomain extracts the subdomain portion from a full domain.
// Returns empty string if domain is not a subdomain of baseDomain.
func ExtractSubdomain(domain, baseDomain string) string {
	domain = strings.ToLower(domain)
	baseDomain = strings.ToLower(baseDomain)
	suffix := "." + baseDomain
	if subdomain, found := strings.CutSuffix(domain, suffix); found {
		return subdomain
	}
	return ""
}
