// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package pages

import (
	"context"
	"errors"
	"fmt"
	"strings"

	repo_model "code.gitea.io/gitea/models/repo"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/json"
	pages_module "code.gitea.io/gitea/modules/pages"
	"code.gitea.io/gitea/modules/setting"
)

const (
	// LandingConfigPath is the path to the landing page configuration file
	LandingConfigPath = ".gitea/landing.yaml"
	// LandingConfigPathAlt is an alternative path for the configuration file
	LandingConfigPathAlt = ".gitea/landing.yml"
)

// GetPagesConfig returns the pages configuration for a repository
// It first tries to load from the database cache, then falls back to parsing the file
func GetPagesConfig(ctx context.Context, repo *repo_model.Repository) (*pages_module.LandingConfig, error) {
	// Check if pages is configured in DB
	dbConfig, err := repo_model.GetPagesConfigByRepoID(ctx, repo.ID)
	if err != nil {
		return nil, err
	}

	// Try to load and parse the config from the repository
	fileConfig, fileHash, err := loadConfigFromRepo(ctx, repo)
	if err != nil {
		// If file doesn't exist, check if DB has config
		if dbConfig != nil && dbConfig.ConfigJSON != "" {
			// Use cached config
			var config pages_module.LandingConfig
			if err := json.Unmarshal([]byte(dbConfig.ConfigJSON), &config); err != nil {
				return nil, err
			}
			return &config, nil
		}
		return nil, err
	}

	// If we have a file config, check if cache needs updating
	if dbConfig != nil && dbConfig.ConfigHash == fileHash {
		// Cache is up to date, use cached config
		var config pages_module.LandingConfig
		if err := json.Unmarshal([]byte(dbConfig.ConfigJSON), &config); err != nil {
			// Cache is corrupted, use file config
			return fileConfig, nil
		}
		return &config, nil
	}

	// Update cache with new config
	configJSON, err := json.Marshal(fileConfig)
	if err != nil {
		return fileConfig, nil // Return config even if caching fails
	}

	if dbConfig == nil {
		// Create new cache entry
		dbConfig = &repo_model.PagesConfig{
			RepoID:     repo.ID,
			Enabled:    fileConfig.Enabled,
			Template:   repo_model.PagesTemplate(fileConfig.Template),
			ConfigJSON: string(configJSON),
			ConfigHash: fileHash,
		}
		if err := repo_model.CreatePagesConfig(ctx, dbConfig); err != nil {
			// Log but don't fail
			return fileConfig, nil
		}
	} else {
		// Update existing cache
		dbConfig.Enabled = fileConfig.Enabled
		dbConfig.Template = repo_model.PagesTemplate(fileConfig.Template)
		dbConfig.ConfigJSON = string(configJSON)
		dbConfig.ConfigHash = fileHash
		if err := repo_model.UpdatePagesConfig(ctx, dbConfig); err != nil {
			// Log but don't fail
			return fileConfig, nil
		}
	}

	return fileConfig, nil
}

// loadConfigFromRepo loads the landing.yaml configuration from the repository
func loadConfigFromRepo(ctx context.Context, repo *repo_model.Repository) (*pages_module.LandingConfig, string, error) {
	gitRepo, err := git.OpenRepository(ctx, repo.RepoPath())
	if err != nil {
		return nil, "", err
	}
	defer gitRepo.Close()

	// Try to get the default branch
	branch := repo.DefaultBranch
	if branch == "" {
		branch = "main"
	}

	// Try to get the commit
	commit, err := gitRepo.GetBranchCommit(branch)
	if err != nil {
		return nil, "", err
	}

	// Try to get the config file
	var content []byte
	entry, err := commit.GetTreeEntryByPath(LandingConfigPath)
	if err != nil {
		// Try alternative path
		entry, err = commit.GetTreeEntryByPath(LandingConfigPathAlt)
		if err != nil {
			return nil, "", errors.New("landing config not found")
		}
	}

	reader, err := entry.Blob().DataAsync()
	if err != nil {
		return nil, "", err
	}
	defer reader.Close()

	content = make([]byte, entry.Blob().Size())
	_, err = reader.Read(content)
	if err != nil && err.Error() != "EOF" {
		return nil, "", err
	}

	// Parse the config
	config, err := pages_module.ParseLandingConfig(content)
	if err != nil {
		return nil, "", err
	}

	// Calculate hash for cache invalidation
	hash := pages_module.HashConfig(content)

	return config, hash, nil
}

// IsPagesEnabled checks if pages is enabled for a repository
func IsPagesEnabled(ctx context.Context, repo *repo_model.Repository) (bool, error) {
	config, err := GetPagesConfig(ctx, repo)
	if err != nil {
		return false, nil // Not enabled if config doesn't exist
	}
	return config.Enabled, nil
}

// EnablePages enables pages for a repository with default config
func EnablePages(ctx context.Context, repo *repo_model.Repository, template string) error {
	if !pages_module.IsValidTemplate(template) {
		template = "simple"
	}

	return repo_model.EnablePages(ctx, repo.ID, repo_model.PagesTemplate(template))
}

// DisablePages disables pages for a repository
func DisablePages(ctx context.Context, repo *repo_model.Repository) error {
	return repo_model.DisablePages(ctx, repo.ID)
}

// GetPagesSubdomain returns the subdomain for a repository's pages
func GetPagesSubdomain(repo *repo_model.Repository) string {
	// Format: {repo}.{owner}.pages.{domain}
	return fmt.Sprintf("%s.%s", strings.ToLower(repo.Name), strings.ToLower(repo.OwnerName))
}

// GetPagesURL returns the full URL for a repository's pages
func GetPagesURL(repo *repo_model.Repository) string {
	subdomain := GetPagesSubdomain(repo)
	// This should be configurable
	pagesDomain := setting.AppURL // TODO: Add proper pages domain setting
	return fmt.Sprintf("https://%s.pages.%s", subdomain, pagesDomain)
}

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

// AddPagesDomain adds a custom domain for pages
func AddPagesDomain(ctx context.Context, repoID int64, domain string) (*repo_model.PagesDomain, error) {
	// Normalize domain
	domain = strings.ToLower(strings.TrimSpace(domain))

	// Check if domain already exists
	existing, err := repo_model.GetPagesDomainByDomain(ctx, domain)
	if err == nil && existing != nil {
		return nil, repo_model.ErrPagesDomainAlreadyExist{Domain: domain}
	}

	pagesDomain := &repo_model.PagesDomain{
		RepoID: repoID,
		Domain: domain,
	}

	if err := repo_model.CreatePagesDomain(ctx, pagesDomain); err != nil {
		return nil, err
	}

	return pagesDomain, nil
}

// RemovePagesDomain removes a custom domain
func RemovePagesDomain(ctx context.Context, repoID, domainID int64) error {
	domain, err := repo_model.GetPagesDomainByID(ctx, domainID)
	if err != nil {
		return err
	}

	// Verify domain belongs to this repo
	if domain.RepoID != repoID {
		return repo_model.ErrPagesDomainNotExist{ID: domainID}
	}

	return repo_model.DeletePagesDomain(ctx, domainID)
}

// VerifyDomain verifies a custom domain by checking DNS records
func VerifyDomain(ctx context.Context, domainID int64) error {
	domain, err := repo_model.GetPagesDomainByID(ctx, domainID)
	if err != nil {
		return err
	}

	// TODO: Implement actual DNS verification
	// For now, just mark as verified
	return repo_model.VerifyPagesDomain(ctx, domain.ID)
}

// GetRepoByPagesDomain returns the repository for a pages domain
func GetRepoByPagesDomain(ctx context.Context, domainName string) (*repo_model.Repository, error) {
	domain, err := repo_model.GetPagesDomainByDomain(ctx, domainName)
	if err != nil {
		return nil, err
	}

	if !domain.Verified {
		return nil, errors.New("domain not verified")
	}

	return repo_model.GetRepositoryByID(ctx, domain.RepoID)
}

// HasPublicLanding checks if a repository has public landing enabled
// This allows private repos to have a public-facing landing page
func HasPublicLanding(ctx context.Context, repo *repo_model.Repository) bool {
	config, err := GetPagesConfig(ctx, repo)
	if err != nil {
		return false
	}
	return config.Enabled && config.PublicLanding
}

// HasPublicReleases checks if a repository has public releases enabled
// This allows private repos to have publicly accessible releases
func HasPublicReleases(ctx context.Context, repo *repo_model.Repository) bool {
	config, err := GetPagesConfig(ctx, repo)
	if err != nil {
		return false
	}
	return config.Enabled && config.Advanced.PublicReleases
}
