// This file is part of the happyDomain (R) project.
// Copyright (c) 2020-2024 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 (
	"encoding/json"
	"time"
)

type Service struct {
	ServiceMeta
	Service ServiceBody `json:"Service"`
}

func (msg *Service) Meta() *ServiceMeta {
	return &msg.ServiceMeta
}

// MarshalJSON computes Comment and NbResources from Service just before
// serialization so that these derived fields are always fresh.
func (s *Service) MarshalJSON() ([]byte, error) {
	if s.Service != nil {
		s.Comment = s.Service.GenComment()
		s.NbResources = s.Service.GetNbResources()
	}

	type serviceAlias Service
	return json.Marshal((*serviceAlias)(s))
}

// ServiceBody represents a service provided by one or more DNS record.
type ServiceBody interface {
	// GetNbResources get the number of main Resources contains in the Service.
	GetNbResources() int

	// GenComment sum up the content of the Service, in a small usefull string.
	GenComment() string

	// GetRecords retrieves underlying RRs.
	GetRecords(domain string, ttl uint32, origin string) ([]Record, error)
}

// MetadataEnricher is implemented by ServiceBody types that store fields
// not derivable from DNS records alone. After re-analysis, EnrichFromPrevious
// is called on the new service body with the previous instance of the same type.
type MetadataEnricher interface {
	EnrichFromPrevious(old ServiceBody)
}

// SPFContributor is implemented by services that contribute SPF directives.
// When multiple services implement this interface for the same domain, their
// directives are merged into a single SPF TXT record (RFC 7208 requires
// exactly one SPF record per domain).
type SPFContributor interface {
	// GetSPFDirectives returns the SPF directives this service contributes
	// (e.g. "include:_spf.google.com", "ip4:203.0.113.0/24").
	// The "v=spf1" prefix and "all" mechanism must NOT be included.
	GetSPFDirectives() []string

	// GetSPFAllPolicy returns the desired "all" policy (e.g. "~all", "-all").
	// Return "" if the service has no opinion; the default is "~all".
	GetSPFAllPolicy() string
}

// ServiceMeta holds the metadata associated to a Service.
type ServiceMeta struct {
	// Type is the string representation of the Service's type.
	Type string `json:"_svctype" binding:"required" readonly:"true"`

	// Id is the Service's identifier.
	Id Identifier `json:"_id,omitempty" swaggertype:"string" readonly:"true"`

	// OwnerId is the User's identifier for the current Service.
	OwnerId Identifier `json:"_ownerid,omitempty" swaggertype:"string"`

	// Domain contains the abstract domain where this Service relates.
	Domain string `json:"_domain" binding:"required"`

	// Ttl contains the specific TTL for the underlying Resources.
	Ttl uint32 `json:"_ttl" binding:"required"`

	// Comment is a string that helps user to distinguish the Service.
	Comment string `json:"_comment,omitempty"`

	// UserComment is a supplementary string defined by the user to
	// distinguish the Service.
	UserComment string `json:"_mycomment,omitempty"`

	// Aliases exposes the aliases defined on this Service.
	Aliases []string `json:"_aliases,omitempty"`

	// NbResources holds the number of Resources stored inside this Service.
	NbResources int `json:"_tmp_hint_nb" binding:"required"`

	// PropagatedAt is the estimated time at which the last published changes
	// for this service will be fully propagated (old cached records expired).
	PropagatedAt *time.Time `json:"_propagated_at,omitempty" format:"date-time"`
}

// ServiceCombined combined ServiceMeta + Service
type ServiceMessage struct {
	ServiceMeta
	Service json.RawMessage `json:"Service"`
}

func (msg *ServiceMessage) Meta() *ServiceMeta {
	return &msg.ServiceMeta
}

type ServiceRecord struct {
	Type   string `json:"type"`
	String string `json:"str"`
	RR     Record `json:"rr,omitempty"`
}

type ServiceUsecase interface {
	ListRecords(*Domain, *Zone, *Service) ([]Record, error)
	ValidateService(ServiceBody, Subdomain, Origin) ([]byte, error)
}
