// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT

package repo

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"strings"

	"code.gitea.io/gitea/models/db"
	"code.gitea.io/gitea/modules/timeutil"
)

// PagesTemplate represents the type of landing page template
type PagesTemplate string

const (
	PagesTemplateSimple        PagesTemplate = "simple"
	PagesTemplateDocumentation PagesTemplate = "documentation"
	PagesTemplateProduct       PagesTemplate = "product"
	PagesTemplatePortfolio     PagesTemplate = "portfolio"
)

// SSLStatus represents the SSL certificate status
type SSLStatus string

const (
	SSLStatusPending  SSLStatus = "pending"
	SSLStatusActive   SSLStatus = "active"
	SSLStatusExpiring SSLStatus = "expiring"
	SSLStatusExpired  SSLStatus = "expired"
	SSLStatusError    SSLStatus = "error"
)

func init() {
	db.RegisterModel(new(PagesDomain))
	db.RegisterModel(new(PagesConfig))
}

// PagesDomain represents a custom domain mapping for Gitea Pages
type PagesDomain struct {
	ID                int64              `xorm:"pk autoincr"`
	RepoID            int64              `xorm:"INDEX NOT NULL"`
	Domain            string             `xorm:"UNIQUE NOT NULL"`
	Verified          bool               `xorm:"DEFAULT false"`
	VerificationToken string             `xorm:"VARCHAR(64)"`
	SSLStatus         SSLStatus          `xorm:"VARCHAR(32) DEFAULT 'pending'"`
	SSLCertExpiry     timeutil.TimeStamp `xorm:"DEFAULT 0"`
	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
	VerifiedUnix      timeutil.TimeStamp `xorm:"DEFAULT 0"`

	Repo *Repository `xorm:"-"`
}

// TableName returns the table name for PagesDomain
func (d *PagesDomain) TableName() string {
	return "pages_domain"
}

// PagesConfig represents the cached configuration for a repository's landing page
type PagesConfig struct {
	ID          int64              `xorm:"pk autoincr"`
	RepoID      int64              `xorm:"UNIQUE NOT NULL"`
	Enabled     bool               `xorm:"DEFAULT false"`
	Template    PagesTemplate      `xorm:"VARCHAR(32) DEFAULT 'simple'"`
	ConfigJSON  string             `xorm:"TEXT"`        // Cached parsed config from landing.yaml
	ConfigHash  string             `xorm:"VARCHAR(64)"` // Hash for invalidation
	UpdatedUnix timeutil.TimeStamp `xorm:"updated"`

	Repo *Repository `xorm:"-"`
}

// TableName returns the table name for PagesConfig
func (c *PagesConfig) TableName() string {
	return "pages_config"
}

// GenerateVerificationToken generates a random token for domain verification
func GenerateVerificationToken() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

// GetPagesDomainByID returns a pages domain by ID
func GetPagesDomainByID(ctx context.Context, id int64) (*PagesDomain, error) {
	domain := new(PagesDomain)
	has, err := db.GetEngine(ctx).ID(id).Get(domain)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, ErrPagesDomainNotExist{ID: id}
	}
	return domain, nil
}

// GetPagesDomainByDomain returns a pages domain by domain name
func GetPagesDomainByDomain(ctx context.Context, domainName string) (*PagesDomain, error) {
	domain := new(PagesDomain)
	has, err := db.GetEngine(ctx).Where("domain = ?", strings.ToLower(domainName)).Get(domain)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, ErrPagesDomainNotExist{Domain: domainName}
	}
	return domain, nil
}

// GetPagesDomainsByRepoID returns all custom domains for a repository
func GetPagesDomainsByRepoID(ctx context.Context, repoID int64) ([]*PagesDomain, error) {
	domains := make([]*PagesDomain, 0, 5)
	return domains, db.GetEngine(ctx).Where("repo_id = ?", repoID).Find(&domains)
}

// CreatePagesDomain creates a new custom domain for pages
func CreatePagesDomain(ctx context.Context, domain *PagesDomain) error {
	// Normalize domain to lowercase
	domain.Domain = strings.ToLower(domain.Domain)

	// Generate verification token
	token, err := GenerateVerificationToken()
	if err != nil {
		return err
	}
	domain.VerificationToken = token
	domain.SSLStatus = SSLStatusPending

	_, err = db.GetEngine(ctx).Insert(domain)
	return err
}

// UpdatePagesDomain updates a pages domain
func UpdatePagesDomain(ctx context.Context, domain *PagesDomain) error {
	_, err := db.GetEngine(ctx).ID(domain.ID).AllCols().Update(domain)
	return err
}

// ActivatePagesDomainSSL sets SSL status to active for a domain
func ActivatePagesDomainSSL(ctx context.Context, id int64) error {
	_, err := db.GetEngine(ctx).ID(id).Cols("ssl_status").Update(&PagesDomain{SSLStatus: SSLStatusActive})
	return err
}

// DeletePagesDomain deletes a pages domain
func DeletePagesDomain(ctx context.Context, id int64) error {
	_, err := db.GetEngine(ctx).ID(id).Delete(new(PagesDomain))
	return err
}

// DeletePagesDomainsByRepoID deletes all pages domains for a repository
func DeletePagesDomainsByRepoID(ctx context.Context, repoID int64) error {
	_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(PagesDomain))
	return err
}

// VerifyPagesDomain marks a domain as verified
func VerifyPagesDomain(ctx context.Context, id int64) error {
	_, err := db.GetEngine(ctx).ID(id).Cols("verified", "verified_unix").Update(&PagesDomain{
		Verified:     true,
		VerifiedUnix: timeutil.TimeStampNow(),
	})
	return err
}

// GetPagesConfigByRepoID returns the pages config for a repository
func GetPagesConfigByRepoID(ctx context.Context, repoID int64) (*PagesConfig, error) {
	config := new(PagesConfig)
	has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Get(config)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, nil // No config means pages not enabled
	}
	return config, nil
}

// CreatePagesConfig creates a new pages config for a repository
func CreatePagesConfig(ctx context.Context, config *PagesConfig) error {
	_, err := db.GetEngine(ctx).Insert(config)
	return err
}

// UpdatePagesConfig updates a pages config
func UpdatePagesConfig(ctx context.Context, config *PagesConfig) error {
	_, err := db.GetEngine(ctx).ID(config.ID).AllCols().Update(config)
	return err
}

// DeletePagesConfig deletes a pages config
func DeletePagesConfig(ctx context.Context, repoID int64) error {
	_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(new(PagesConfig))
	return err
}

// EnablePages enables pages for a repository
func EnablePages(ctx context.Context, repoID int64, template PagesTemplate) error {
	config, err := GetPagesConfigByRepoID(ctx, repoID)
	if err != nil {
		return err
	}

	if config == nil {
		// Create new config
		config = &PagesConfig{
			RepoID:   repoID,
			Enabled:  true,
			Template: template,
		}
		return CreatePagesConfig(ctx, config)
	}

	// Update existing config
	config.Enabled = true
	config.Template = template
	return UpdatePagesConfig(ctx, config)
}

// DisablePages disables pages for a repository
func DisablePages(ctx context.Context, repoID int64) error {
	config, err := GetPagesConfigByRepoID(ctx, repoID)
	if err != nil {
		return err
	}

	if config == nil {
		return nil // Already disabled
	}

	config.Enabled = false
	return UpdatePagesConfig(ctx, config)
}

// IsPagesEnabled checks if pages is enabled for a repository
func IsPagesEnabled(ctx context.Context, repoID int64) (bool, error) {
	config, err := GetPagesConfigByRepoID(ctx, repoID)
	if err != nil {
		return false, err
	}
	return config != nil && config.Enabled, nil
}

// ErrPagesDomainNotExist represents a "pages domain not exist" error
type ErrPagesDomainNotExist struct {
	ID     int64
	Domain string
}

func (err ErrPagesDomainNotExist) Error() string {
	if err.Domain != "" {
		return fmt.Sprintf("pages domain does not exist [domain: %s]", err.Domain)
	}
	return fmt.Sprintf("pages domain does not exist [id: %d]", err.ID)
}

// IsErrPagesDomainNotExist checks if an error is ErrPagesDomainNotExist
func IsErrPagesDomainNotExist(err error) bool {
	_, ok := err.(ErrPagesDomainNotExist)
	return ok
}

// ErrPagesDomainAlreadyExist represents a "pages domain already exist" error
type ErrPagesDomainAlreadyExist struct {
	Domain string
}

func (err ErrPagesDomainAlreadyExist) Error() string {
	return fmt.Sprintf("pages domain already exists [domain: %s]", err.Domain)
}

// IsErrPagesDomainAlreadyExist checks if an error is ErrPagesDomainAlreadyExist
func IsErrPagesDomainAlreadyExist(err error) bool {
	_, ok := err.(ErrPagesDomainAlreadyExist)
	return ok
}

// ErrPagesConfigNotExist represents a "pages config not exist" error
type ErrPagesConfigNotExist struct {
	RepoID int64
}

func (err ErrPagesConfigNotExist) Error() string {
	return fmt.Sprintf("pages config does not exist [repo_id: %d]", err.RepoID)
}

// IsErrPagesConfigNotExist checks if an error is ErrPagesConfigNotExist
func IsErrPagesConfigNotExist(err error) bool {
	_, ok := err.(ErrPagesConfigNotExist)
	return ok
}

// GetPagesConfig returns the pages config for a repository with proper error handling
func GetPagesConfig(ctx context.Context, repoID int64) (*PagesConfig, error) {
	config := new(PagesConfig)
	has, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Get(config)
	if err != nil {
		return nil, err
	}
	if !has {
		return nil, ErrPagesConfigNotExist{RepoID: repoID}
	}
	return config, nil
}

// GetPagesDomains returns all custom domains for a repository
func GetPagesDomains(ctx context.Context, repoID int64) ([]*PagesDomain, error) {
	return GetPagesDomainsByRepoID(ctx, repoID)
}
