// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2026 happyDomain
// Authors: Pierre-Olivier Mercier, et al.
//
// This program is offered under a commercial and under the AGPL license.
// For commercial licensing, contact us at <contact@happydomain.org>.
//
// For AGPL licensing:
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package happydns

import (
	"context"
	"encoding/json"
	"time"

	sdk "git.happydns.org/checker-sdk-go/checker"
)

// The types and helpers needed by external checker plugins live in the
// Apache-2.0 licensed checker-sdk-go module. They are re-exported here as
// aliases so the rest of the happyDomain codebase keeps relying on this model.
//
// Host-only types (Execution, CheckPlan, CheckEvaluation, …) remain
// defined in this file because they describe orchestration state that is
// internal to the happyDomain server and never crosses the plugin boundary.

// --- Re-exports from checker-sdk-go ---

type CheckScopeType = sdk.CheckScopeType

const (
	CheckScopeAdmin   = sdk.CheckScopeAdmin
	CheckScopeUser    = sdk.CheckScopeUser
	CheckScopeDomain  = sdk.CheckScopeDomain
	CheckScopeZone    = sdk.CheckScopeZone
	CheckScopeService = sdk.CheckScopeService
)

const (
	AutoFillDomainName       = sdk.AutoFillDomainName
	AutoFillSubdomain        = sdk.AutoFillSubdomain
	AutoFillZone             = sdk.AutoFillZone
	AutoFillServiceType      = sdk.AutoFillServiceType
	AutoFillService          = sdk.AutoFillService
	AutoFillDiscoveryEntries = sdk.AutoFillDiscoveryEntries
)

type (
	CheckTarget                 = sdk.CheckTarget
	CheckerAvailability         = sdk.CheckerAvailability
	CheckerOptions              = sdk.CheckerOptions
	CheckerOptionDocumentation  = sdk.CheckerOptionDocumentation
	CheckerOptionsDocumentation = sdk.CheckerOptionsDocumentation
	Status                      = sdk.Status
	CheckState                  = sdk.CheckState
	CheckMetric                 = sdk.CheckMetric
	ObservationKey              = sdk.ObservationKey
	CheckIntervalSpec           = sdk.CheckIntervalSpec
	ObservationProvider         = sdk.ObservationProvider
	CheckRuleInfo               = sdk.CheckRuleInfo
	CheckRule                   = sdk.CheckRule
	CheckRuleWithOptions        = sdk.CheckRuleWithOptions
	RulePrecheck                = sdk.RulePrecheck
	RulePrecheckRequest         = sdk.RulePrecheckRequest
	RulePrecheckResponse        = sdk.RulePrecheckResponse
	ObservationGetter           = sdk.ObservationGetter
	CheckAggregator             = sdk.CheckAggregator
	CheckerHTMLReporter         = sdk.CheckerHTMLReporter
	CheckerMetricsReporter      = sdk.CheckerMetricsReporter
	CheckerDefinitionProvider   = sdk.CheckerDefinitionProvider
	CheckerDefinition           = sdk.CheckerDefinition
	OptionsValidator            = sdk.OptionsValidator
	ExternalCollectRequest      = sdk.ExternalCollectRequest
	ExternalCollectResponse     = sdk.ExternalCollectResponse
	ExternalEvaluateRequest     = sdk.ExternalEvaluateRequest
	ExternalEvaluateResponse    = sdk.ExternalEvaluateResponse
	ExternalReportRequest       = sdk.ExternalReportRequest
	DiscoveryEntry              = sdk.DiscoveryEntry
	DiscoveryPublisher          = sdk.DiscoveryPublisher
	RelatedObservation          = sdk.RelatedObservation
	ReportContext               = sdk.ReportContext
)

const (
	StatusOK      = sdk.StatusOK
	StatusInfo    = sdk.StatusInfo
	StatusUnknown = sdk.StatusUnknown
	StatusWarn    = sdk.StatusWarn
	StatusCrit    = sdk.StatusCrit
	StatusError   = sdk.StatusError
)

// --- Helpers for converting between target identifier strings and *Identifier ---

// TargetIdentifier parses a target identifier string into an *Identifier.
// Returns nil if the string is empty or cannot be parsed.
func TargetIdentifier(s string) *Identifier {
	if s == "" {
		return nil
	}
	id, err := NewIdentifierFromString(s)
	if err != nil {
		return nil
	}
	return &id
}

// FormatIdentifier returns the string representation of id, or "" if nil.
func FormatIdentifier(id *Identifier) string {
	if id == nil {
		return ""
	}
	return id.String()
}

// --- Host-only types (orchestration state) ---

// CheckerRunRequest is the JSON body for manually triggering a checker.
type CheckerRunRequest struct {
	Options      CheckerOptions  `json:"options,omitempty"`
	EnabledRules map[string]bool `json:"enabledRules,omitempty"`
}

// CheckerOptionsPositional stores options with their positional key components.
type CheckerOptionsPositional struct {
	CheckName string      `json:"checkName"`
	UserId    *Identifier `json:"userId,omitempty"`
	DomainId  *Identifier `json:"domainId,omitempty"`
	ServiceId *Identifier `json:"serviceId,omitempty"`

	Options CheckerOptions `json:"options"`
}

// CheckPlan is an optional user override for a checker on a specific target.
type CheckPlan struct {
	Id        Identifier      `json:"id" swaggertype:"string" validate:"required" readonly:"true"`
	CheckerID string          `json:"checkerId" validate:"required" readonly:"true"`
	Target    CheckTarget     `json:"target" validate:"required" readonly:"true"`
	Interval  *time.Duration  `json:"interval,omitempty" swaggertype:"integer"`
	Enabled   map[string]bool `json:"enabled,omitempty"`
	// Disabled, when true, pauses the automatic scheduling of this plan
	// regardless of per-rule Enabled state. Manual runs are unaffected.
	Disabled bool `json:"disabled,omitempty"`
}

// IsFullyDisabled returns true if the enabled map is non-empty and every entry is false.
func (p *CheckPlan) IsFullyDisabled() bool {
	if len(p.Enabled) == 0 {
		return false
	}
	for _, v := range p.Enabled {
		if v {
			return false
		}
	}
	return true
}

// IsRuleEnabled returns whether a specific rule is enabled.
// A nil or empty map means all rules are enabled. A missing key means enabled.
func (p *CheckPlan) IsRuleEnabled(ruleName string) bool {
	if len(p.Enabled) == 0 {
		return true
	}
	v, ok := p.Enabled[ruleName]
	if !ok {
		return true
	}
	return v
}

// CheckerStatus combines a checker definition with its latest execution and plan for a target.
//
// PrecheckFailures is the per-rule precheck outcome for the effective
// options resolved at the target's scope: keys are rule names, values
// are the reason the rule cannot run (typically "<X> API key is not
// configured"). Rules that pass their precheck — or do not implement
// one — are absent. Empty/omitted means "no rule is currently gated".
type CheckerStatus struct {
	*CheckerDefinition
	LatestExecution  *Execution        `json:"latestExecution,omitempty"`
	Plan             *CheckPlan        `json:"plan,omitempty"`
	Enabled          bool              `json:"enabled"`
	EnabledRules     map[string]bool   `json:"enabledRules"`
	PrecheckFailures map[string]string `json:"precheckFailures,omitempty"`
}

// CheckEvaluation is the result of running a checker on observed data.
type CheckEvaluation struct {
	Id          Identifier   `json:"id" swaggertype:"string" binding:"required" readonly:"true"`
	PlanID      *Identifier  `json:"planId,omitempty" swaggertype:"string"`
	CheckerID   string       `json:"checkerId" binding:"required"`
	Target      CheckTarget  `json:"target" binding:"required"`
	SnapshotID  Identifier   `json:"snapshotId" swaggertype:"string" binding:"required" readonly:"true"`
	EvaluatedAt time.Time    `json:"evaluatedAt" binding:"required" readonly:"true" format:"date-time"`
	States      []CheckState `json:"states" binding:"required" readonly:"true"`
}

// ObservationSnapshot holds data collected during an execution.
type ObservationSnapshot struct {
	Id          Identifier                         `json:"id" swaggertype:"string" binding:"required" readonly:"true"`
	Target      CheckTarget                        `json:"target" binding:"required" readonly:"true"`
	CollectedAt time.Time                          `json:"collectedAt" binding:"required" readonly:"true" format:"date-time"`
	Data        map[ObservationKey]json.RawMessage `json:"data" binding:"required" readonly:"true" swaggertype:"object,object"`
}

// ObservationCacheEntry is a lightweight pointer to cached observation data in a snapshot.
//
// EnabledRulesHash fingerprints the rule-enable map that was in effect when
// the observation was collected. The engine recomputes the hash before
// reading the cache and bypasses the freshness window if it does not match,
// so a re-enabled rule does not consume stale data collected while it was
// disabled. Empty string means "no rule constraint was recorded" (legacy
// entries or full-default executions).
type ObservationCacheEntry struct {
	SnapshotID       Identifier `json:"snapshotId"`
	CollectedAt      time.Time  `json:"collectedAt"`
	EnabledRulesHash string     `json:"enabledRulesHash,omitempty"`
}

// StoredDiscoveryEntry is the host-side persistent form of a DiscoveryEntry:
// the opaque SDK-level (Type, Ref, Payload) triple augmented with the
// producing checker and target — the namespace under which the host dedupes
// and replaces entries across collection cycles.
type StoredDiscoveryEntry struct {
	ProducerID string          `json:"producerId"`
	Target     CheckTarget     `json:"target"`
	Type       string          `json:"type"`
	Ref        string          `json:"ref"`
	Payload    json.RawMessage `json:"payload,omitempty" swaggertype:"object"`
}

// DiscoveryObservationRef links a consumer's observation to a specific
// DiscoveryEntry it covered. It lets the host resolve
// ObservationGetter.GetRelated and ReportContext.Related without re-parsing
// snapshots.
type DiscoveryObservationRef struct {
	ProducerID  string         `json:"producerId"`
	Target      CheckTarget    `json:"target"`
	Ref         string         `json:"ref"`
	ConsumerID  string         `json:"consumerId"`
	Key         ObservationKey `json:"key"`
	SnapshotID  Identifier     `json:"snapshotId" swaggertype:"string"`
	CollectedAt time.Time      `json:"collectedAt" format:"date-time"`
}

// ExecutionStatus represents the lifecycle state of an execution.
type ExecutionStatus int

const (
	ExecutionPending ExecutionStatus = iota
	ExecutionRunning
	ExecutionDone
	ExecutionFailed
	// ExecutionRateLimited indicates a planned execution that will be
	// skipped because the user's MaxChecksPerDay quota is exhausted.
	// Only used for synthetic planned executions returned by
	// ListPlannedExecutions; never persisted.
	ExecutionRateLimited
)

// TriggerType represents what initiated an execution.
type TriggerType int

const (
	TriggerManual TriggerType = iota
	TriggerSchedule
)

// TriggerInfo describes the trigger for an execution.
type TriggerInfo struct {
	Type   TriggerType `json:"type"`
	PlanID *Identifier `json:"planId,omitempty" swaggertype:"string"`
}

// Execution represents a single run of a checker pipeline.
type Execution struct {
	Id           Identifier      `json:"id" swaggertype:"string" binding:"required" readonly:"true"`
	CheckerID    string          `json:"checkerId" binding:"required" readonly:"true"`
	PlanID       *Identifier     `json:"planId,omitempty" swaggertype:"string" readonly:"true"`
	Target       CheckTarget     `json:"target" binding:"required" readonly:"true"`
	Trigger      TriggerInfo     `json:"trigger" binding:"required" readonly:"true"`
	StartedAt    time.Time       `json:"startedAt" binding:"required" readonly:"true" format:"date-time"`
	EndedAt      *time.Time      `json:"endedAt,omitempty" readonly:"true" format:"date-time"`
	Status       ExecutionStatus `json:"status" binding:"required" readonly:"true"`
	Error        string          `json:"error,omitempty" readonly:"true"`
	Result       CheckState      `json:"result" readonly:"true"`
	EvaluationID *Identifier     `json:"evaluationId,omitempty" swaggertype:"string" readonly:"true"`
}

// CheckerEngine orchestrates the full checker pipeline.
type CheckerEngine interface {
	CreateExecution(checkerID string, target CheckTarget, plan *CheckPlan) (*Execution, error)
	RunExecution(ctx context.Context, exec *Execution, plan *CheckPlan, runOpts CheckerOptions) (*CheckEvaluation, error)
}
