package page

import (
	"context"
	"fmt"
	"time"

	"github.com/gocql/gocql"
	"go.uber.org/zap"

	domainpage "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/page"
	domainsite "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/domain/site"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/search"
	"codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/security/ipcrypt"
)

// SyncPagesUseCase handles page synchronization from WordPress
type SyncPagesUseCase struct {
	pageRepo     domainpage.Repository
	siteRepo     domainsite.Repository
	searchClient *search.Client
	ipEncryptor  *ipcrypt.IPEncryptor
	logger       *zap.Logger
}

// ProvideSyncPagesUseCase creates a new SyncPagesUseCase
func ProvideSyncPagesUseCase(
	pageRepo domainpage.Repository,
	siteRepo domainsite.Repository,
	searchClient *search.Client,
	ipEncryptor *ipcrypt.IPEncryptor,
	logger *zap.Logger,
) *SyncPagesUseCase {
	return &SyncPagesUseCase{
		pageRepo:     pageRepo,
		siteRepo:     siteRepo,
		searchClient: searchClient,
		ipEncryptor:  ipEncryptor,
		logger:       logger,
	}
}

// SyncPageInput represents a single page to sync
type SyncPageInput struct {
	PageID      string    `json:"page_id"`
	Title       string    `json:"title"`
	Content     string    `json:"content"`
	Excerpt     string    `json:"excerpt"`
	URL         string    `json:"url"`
	Status      string    `json:"status"`    // publish, draft, trash
	PostType    string    `json:"post_type"` // page, post
	Author      string    `json:"author"`
	PublishedAt time.Time `json:"published_at"`
	ModifiedAt  time.Time `json:"modified_at"`
	IPAddress   string    `json:"-"` // Plain IP address (will be encrypted before storage), never exposed in JSON
}

// SyncPagesInput is the input for syncing pages
type SyncPagesInput struct {
	Pages []SyncPageInput `json:"pages"`
}

// SyncPagesOutput is the output after syncing pages
type SyncPagesOutput struct {
	SyncedCount  int      `json:"synced_count"`
	IndexedCount int      `json:"indexed_count"`
	FailedPages  []string `json:"failed_pages,omitempty"`
	Message      string   `json:"message"`
}

// SyncPages syncs a batch of pages for a site
func (uc *SyncPagesUseCase) SyncPages(ctx context.Context, tenantID, siteID gocql.UUID, input *SyncPagesInput) (*SyncPagesOutput, error) {
	uc.logger.Info("syncing pages",
		zap.String("tenant_id", tenantID.String()),
		zap.String("site_id", siteID.String()),
		zap.Int("page_count", len(input.Pages)))

	// Get site to validate and check quotas
	site, err := uc.siteRepo.GetByID(ctx, tenantID, siteID)
	if err != nil {
		uc.logger.Error("failed to get site", zap.Error(err))
		return nil, domainsite.ErrSiteNotFound
	}

	// Verify site is verified (skip for test mode)
	if site.RequiresVerification() && !site.IsVerified {
		uc.logger.Warn("site not verified", zap.String("site_id", siteID.String()))
		return nil, domainsite.ErrSiteNotVerified
	}

	// No quota limits - usage-based billing (anti-abuse via rate limiting only)

	// Ensure search index exists
	indexExists, err := uc.searchClient.IndexExists(siteID.String())
	if err != nil {
		uc.logger.Error("failed to check index existence", zap.Error(err))
		return nil, fmt.Errorf("failed to check search index: %w", err)
	}

	if !indexExists {
		uc.logger.Info("creating search index", zap.String("site_id", siteID.String()))
		if err := uc.searchClient.CreateIndex(siteID.String()); err != nil {
			uc.logger.Error("failed to create index", zap.Error(err))
			return nil, fmt.Errorf("failed to create search index: %w", err)
		}
	}

	// Process each page
	syncedCount := 0
	indexedCount := 0
	var failedPages []string
	var documentsToIndex []search.PageDocument

	for _, pageInput := range input.Pages {
		// Encrypt IP address (CWE-359: GDPR compliance)
		encryptedIP, err := uc.ipEncryptor.Encrypt(pageInput.IPAddress)
		if err != nil {
			uc.logger.Error("failed to encrypt IP address",
				zap.String("page_id", pageInput.PageID),
				zap.Error(err))
			failedPages = append(failedPages, pageInput.PageID)
			continue
		}

		// Create page entity
		page := domainpage.NewPage(
			siteID,
			site.TenantID,
			pageInput.PageID,
			pageInput.Title,
			pageInput.Content,
			pageInput.Excerpt,
			pageInput.URL,
			pageInput.Status,
			pageInput.PostType,
			pageInput.Author,
			pageInput.PublishedAt,
			pageInput.ModifiedAt,
			encryptedIP,
		)

		// Upsert page to database
		if err := uc.pageRepo.Upsert(ctx, page); err != nil {
			uc.logger.Error("failed to upsert page",
				zap.String("page_id", pageInput.PageID),
				zap.Error(err))
			failedPages = append(failedPages, pageInput.PageID)
			continue
		}

		syncedCount++

		// Only index published pages
		if page.ShouldIndex() {
			page.MarkIndexed()

			// Prepare document for Meilisearch
			doc := search.PageDocument{
				ID:          page.PageID,
				SiteID:      page.SiteID.String(),
				TenantID:    page.TenantID.String(),
				Title:       page.Title,
				Content:     page.Content,
				Excerpt:     page.Excerpt,
				URL:         page.URL,
				Status:      page.Status,
				PostType:    page.PostType,
				Author:      page.Author,
				PublishedAt: page.PublishedAt.Unix(),
				ModifiedAt:  page.ModifiedAt.Unix(),
			}

			documentsToIndex = append(documentsToIndex, doc)
		}
	}

	// Index documents in Meilisearch if any
	if len(documentsToIndex) > 0 {
		_, err := uc.searchClient.AddDocuments(siteID.String(), documentsToIndex)
		if err != nil {
			uc.logger.Error("failed to index documents", zap.Error(err))
			return nil, fmt.Errorf("failed to index documents: %w", err)
		}
		indexedCount = len(documentsToIndex)
		// Note: Usage tracking is handled by the service layer via UpdateSiteUsageUseCase
	}

	uc.logger.Info("pages synced successfully",
		zap.String("site_id", siteID.String()),
		zap.Int("synced", syncedCount),
		zap.Int("indexed", indexedCount),
		zap.Int("failed", len(failedPages)))

	message := fmt.Sprintf("Successfully synced %d pages, indexed %d pages", syncedCount, indexedCount)
	if len(failedPages) > 0 {
		message += fmt.Sprintf(", failed %d pages", len(failedPages))
	}

	return &SyncPagesOutput{
		SyncedCount:  syncedCount,
		IndexedCount: indexedCount,
		FailedPages:  failedPages,
		Message:      message,
	}, nil
}
