package audit

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"

	domainrls "github.com/nexuscrm/kernel/pkg/rls"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

const defaultHistoryPageSize = 200

type HistoryMetadataProvider interface {
	GetSchema(ctx context.Context, objectAPIName string) *models.ObjectMetadata
}

type HistoryPermissionProvider interface {
	CheckObjectPermissionWithUser(ctx context.Context, objectAPIName string, operation string, user *models.UserSession) bool
	BuildRLSResult(ctx context.Context, schema *models.ObjectMetadata, objectAPIName string, user *models.UserSession) *domainrls.Result
	CheckFieldVisibilityWithUser(ctx context.Context, objectAPIName string, fieldAPIName string, user *models.UserSession) (bool, error)
}

type HistoryViewResolver interface {
	ResolveTableName(ctx context.Context, schema *models.ObjectMetadata) string
}

const (
	HistoryQueryErrorObjectPermissionDenied = contract.HistoryQueryErrorObjectPermissionDenied
	HistoryQueryErrorRecordVisibilityDenied = contract.HistoryQueryErrorRecordVisibilityDenied
	HistoryQueryErrorMalformed              = contract.HistoryQueryErrorMalformed
	HistoryQueryErrorUnavailable            = contract.HistoryQueryErrorUnavailable
	HistoryQueryErrorInternal               = contract.HistoryQueryErrorInternal
)

type HistoryQueryError = contract.HistoryQueryError

type HistoryQueryService struct {
	db           *sql.DB
	metadata     HistoryMetadataProvider
	permissions  HistoryPermissionProvider
	viewResolver HistoryViewResolver
}

func NewHistoryQueryService(
	db *sql.DB,
	metadata HistoryMetadataProvider,
	permissions HistoryPermissionProvider,
	viewResolver HistoryViewResolver,
) *HistoryQueryService {
	return &HistoryQueryService{
		db:           db,
		metadata:     metadata,
		permissions:  permissions,
		viewResolver: viewResolver,
	}
}

func (s *HistoryQueryService) QueryHistory(ctx context.Context, req contract.HistoryQueryRequest, user *models.UserSession) (*contract.HistoryQueryResult, error) {
	if req.PageSize <= 0 {
		req.PageSize = defaultHistoryPageSize
	}
	if req.CursorOffset < 0 {
		req.CursorOffset = 0
	}

	parentObject := strings.TrimSpace(req.ParentObjectAPIName)
	if s == nil || s.permissions == nil || !s.permissions.CheckObjectPermissionWithUser(ctx, parentObject, constants.PermRead, user) {
		return nil, &HistoryQueryError{
			Kind:    HistoryQueryErrorObjectPermissionDenied,
			Message: fmt.Sprintf("insufficient access to read %s history", parentObject),
		}
	}

	parentSchema := s.parentSchema(ctx, parentObject)
	parentReadTable := s.historyReadTable(ctx, parentSchema, parentObject)
	rlsResult := s.permissions.BuildRLSResult(ctx, parentSchema, parentObject, user)
	if rlsResult == nil || rlsResult.DenyAll {
		return nil, &HistoryQueryError{
			Kind:    HistoryQueryErrorRecordVisibilityDenied,
			Message: fmt.Sprintf("insufficient access to read %s records", parentObject),
		}
	}

	parentVisibilityWhere := ""
	if rlsResult != nil && !rlsResult.Skip {
		parentVisibilityWhere = domainrls.ToSQLFilter(rlsResult, parentReadTable)
	}
	if deletedWhere := HistoryParentDeletedWhere(parentSchema, parentReadTable, req.ScanAll); deletedWhere != "" {
		if strings.TrimSpace(parentVisibilityWhere) == "" {
			parentVisibilityWhere = deletedWhere
		} else {
			parentVisibilityWhere = fmt.Sprintf("(%s) AND %s", parentVisibilityWhere, deletedWhere)
		}
	}

	plan, err := BuildHistoryQueryPlan(parentObject, req.SelectFields)
	if err != nil {
		return nil, &HistoryQueryError{Kind: HistoryQueryErrorMalformed, Message: err.Error(), Cause: err}
	}
	plan, err = applyHistoryQueryFilter(plan, req.Filter)
	if err != nil {
		return nil, &HistoryQueryError{Kind: HistoryQueryErrorMalformed, Message: err.Error(), Cause: err}
	}

	perms := &historyUserPerms{svc: s.permissions, ctx: ctx, user: user}
	plan.AllowedFields, err = AllowedHistoryFieldNames(parentSchema, perms)
	if err != nil {
		return nil, &HistoryQueryError{Kind: HistoryQueryErrorUnavailable, Message: "field permission authority unavailable", Cause: err}
	}
	redacted, err := RedactFieldHistoryPlan(plan, perms)
	if err != nil {
		var denied *FieldDeniedByFLSError
		if errors.As(err, &denied) {
			return nil, err
		}
		return nil, &HistoryQueryError{Kind: HistoryQueryErrorUnavailable, Message: "field permission authority unavailable", Cause: err}
	}

	if s.db == nil {
		return nil, &HistoryQueryError{Kind: HistoryQueryErrorUnavailable, Message: "audit backend not initialized"}
	}
	totalSize, err := CountHistoryPlan(ctx, s.db, redacted, req.ParentID, parentVisibilityWhere, parentReadTable)
	if err != nil {
		return nil, &HistoryQueryError{Kind: HistoryQueryErrorInternal, Message: "audit history query failed", Cause: err}
	}
	if req.Offset > 0 {
		if totalSize <= req.Offset {
			totalSize = 0
		} else {
			totalSize -= req.Offset
		}
	}
	if req.Limit > 0 && totalSize > req.Limit {
		totalSize = req.Limit
	}

	pageSize := req.PageSize
	if req.Limit > 0 {
		remaining := req.Limit - req.CursorOffset
		if remaining <= 0 {
			pageSize = 0
		} else if remaining < pageSize {
			pageSize = remaining
		}
	}
	queryOffset := req.Offset + req.CursorOffset
	rows, err := ExecuteHistoryPlan(ctx, s.db, redacted, req.ParentID, pageSize+1, queryOffset, req.SortDesc, parentVisibilityWhere, parentReadTable)
	if err != nil {
		return nil, &HistoryQueryError{Kind: HistoryQueryErrorInternal, Message: "audit history query failed", Cause: err}
	}
	if len(rows) > pageSize {
		rows = rows[:pageSize]
	}

	result := &contract.HistoryQueryResult{
		TotalSize: totalSize,
		Done:      req.CursorOffset+len(rows) >= totalSize,
		Rows:      make([]contract.HistoryRecord, len(rows)),
	}
	for i, row := range rows {
		result.Rows[i] = contract.HistoryRecord{
			ID:          row.ID,
			ParentID:    row.ParentID,
			Field:       row.Field,
			OldValue:    row.OldValue,
			NewValue:    row.NewValue,
			DataType:    row.DataType,
			CreatedDate: row.CreatedDate,
			CreatedByID: row.CreatedByID,
		}
	}
	return result, nil
}

func (s *HistoryQueryService) parentSchema(ctx context.Context, objectAPIName string) *models.ObjectMetadata {
	if s == nil || s.metadata == nil {
		return nil
	}
	return s.metadata.GetSchema(ctx, objectAPIName)
}

func (s *HistoryQueryService) historyReadTable(ctx context.Context, schema *models.ObjectMetadata, fallback string) string {
	var resolver HistoryViewResolver
	if s != nil {
		resolver = s.viewResolver
	}
	return HistoryReadTable(ctx, schema, fallback, resolver)
}

type historyUserPerms struct {
	svc  HistoryPermissionProvider
	ctx  context.Context
	user *models.UserSession
}

func (p *historyUserPerms) HasFieldRead(objectAPIName, fieldAPIName string) (bool, error) {
	if p == nil || p.svc == nil {
		return false, nil
	}
	return p.svc.CheckFieldVisibilityWithUser(p.ctx, objectAPIName, fieldAPIName, p.user)
}

func historySQLIdentifier(name string) string {
	return strings.ReplaceAll(strings.TrimSpace(name), "`", "``")
}

func historyFieldColumnName(field models.FieldMetadata) string {
	physical := strings.TrimSpace(field.PhysicalName)
	if physical == "" || (strings.HasPrefix(physical, "`") && strings.HasSuffix(physical, "`")) {
		return field.APIName
	}
	return physical
}

func HistoryReadTable(ctx context.Context, schema *models.ObjectMetadata, fallback string, resolver HistoryViewResolver) string {
	if resolver != nil && schema != nil {
		if resolved := strings.TrimSpace(resolver.ResolveTableName(ctx, schema)); resolved != "" {
			return resolved
		}
	}
	return fallback
}

func HistoryParentDeletedWhere(schema *models.ObjectMetadata, readTable string, scanAll bool) string {
	if scanAll || schema == nil {
		return ""
	}
	table := strings.TrimSpace(readTable)
	if table == "" {
		table = schema.APIName
	}
	for _, field := range schema.Fields {
		if !strings.EqualFold(field.APIName, constants.FieldIsDeleted) {
			continue
		}
		col := historyFieldColumnName(field)
		tableIdent := historySQLIdentifier(table)
		colIdent := historySQLIdentifier(col)
		return fmt.Sprintf("(`%s`.`%s` = %d OR `%s`.`%s` IS NULL)",
			tableIdent, colIdent, constants.IsDeletedFalse,
			tableIdent, colIdent,
		)
	}
	return ""
}
