package teams

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"

	"github.com/nicholas-fedor/shoutrrr/pkg/format"
	"github.com/nicholas-fedor/shoutrrr/pkg/services/standard"
	"github.com/nicholas-fedor/shoutrrr/pkg/types"
)

// Scheme is the identifier for the Teams service protocol.
const Scheme = "teams"

// Config constants.
const (
	DummyURL           = "teams://dummy@dummy.com" // Default placeholder URL
	ExpectedOrgMatches = 2                         // Full match plus organization domain capture group
	MinPathComponents  = 3                         // Minimum required path components: AltID, GroupOwner, ExtraID
)

// Config represents the configuration for the Teams service.
type Config struct {
	standard.EnumlessConfig
	Group      string `optional:"" url:"user"`
	Tenant     string `optional:"" url:"host"`
	AltID      string `optional:"" url:"path1"`
	GroupOwner string `optional:"" url:"path2"`
	ExtraID    string `optional:"" url:"path3"`

	Title string `key:"title" optional:""`
	Color string `key:"color" optional:""`
	Host  string `key:"host"  optional:""` // Required, no default
}

// WebhookParts returns the webhook components as an array.
func (config *Config) WebhookParts() [5]string {
	return [5]string{config.Group, config.Tenant, config.AltID, config.GroupOwner, config.ExtraID}
}

// SetFromWebhookURL updates the Config from a Teams webhook URL.
func (config *Config) SetFromWebhookURL(webhookURL string) error {
	orgPattern := regexp.MustCompile(
		`https://([a-zA-Z0-9-\.]+)` + WebhookDomain + `/` + Path + `/([0-9a-f\-]{36})@([0-9a-f\-]{36})/` + ProviderName + `/([0-9a-f]{32})/([0-9a-f\-]{36})/([^/]+)`,
	)

	orgGroups := orgPattern.FindStringSubmatch(webhookURL)
	if len(orgGroups) != ExpectedComponents {
		return ErrInvalidWebhookFormat
	}

	config.Host = orgGroups[1] + ".webhook.office.com"

	parts, err := ParseAndVerifyWebhookURL(webhookURL)
	if err != nil {
		return err
	}

	config.setFromWebhookParts(parts)

	return nil
}

// ConfigFromWebhookURL creates a new Config from a parsed Teams webhook URL.
func ConfigFromWebhookURL(webhookURL url.URL) (*Config, error) {
	webhookURL.RawQuery = ""
	config := &Config{Host: webhookURL.Host}

	if err := config.SetFromWebhookURL(webhookURL.String()); err != nil {
		return nil, err
	}

	return config, nil
}

// GetURL constructs a URL from the Config fields.
func (config *Config) GetURL() *url.URL {
	resolver := format.NewPropKeyResolver(config)

	return config.getURL(&resolver)
}

// getURL constructs a URL using the provided resolver.
func (config *Config) getURL(resolver types.ConfigQueryResolver) *url.URL {
	if config.Host == "" {
		return nil
	}

	return &url.URL{
		User:     url.User(config.Group),
		Host:     config.Tenant,
		Path:     "/" + config.AltID + "/" + config.GroupOwner + "/" + config.ExtraID,
		Scheme:   Scheme,
		RawQuery: format.BuildQuery(resolver),
	}
}

// SetURL updates the Config from a URL.
func (config *Config) SetURL(url *url.URL) error {
	resolver := format.NewPropKeyResolver(config)

	return config.setURL(&resolver, url)
}

// setURL updates the Config from a URL using the provided resolver.
// It parses the URL parts, sets query parameters, and ensures the host is specified.
// Returns an error if the URL is invalid or the host is missing.
func (config *Config) setURL(resolver types.ConfigQueryResolver, url *url.URL) error {
	parts, err := parseURLParts(url)
	if err != nil {
		return err
	}

	config.setFromWebhookParts(parts)

	if err := config.setQueryParams(resolver, url.Query()); err != nil {
		return err
	}

	// Allow dummy URL during documentation generation
	if config.Host == "" && (url.User != nil && url.User.Username() == "dummy") {
		config.Host = "dummy.webhook.office.com"
	} else if config.Host == "" {
		return ErrMissingHostParameter
	}

	return nil
}

// parseURLParts extracts and validates webhook components from a URL.
func parseURLParts(url *url.URL) ([5]string, error) {
	var parts [5]string
	if url.String() == DummyURL {
		return parts, nil
	}

	pathParts := strings.Split(url.Path, "/")
	if pathParts[0] == "" {
		pathParts = pathParts[1:]
	}

	if len(pathParts) < MinPathComponents {
		return parts, ErrMissingExtraIDComponent
	}

	parts = [5]string{
		url.User.Username(),
		url.Hostname(),
		pathParts[0],
		pathParts[1],
		pathParts[2],
	}
	if err := verifyWebhookParts(parts); err != nil {
		return parts, fmt.Errorf("invalid URL format: %w", err)
	}

	return parts, nil
}

// setQueryParams applies query parameters to the Config using the resolver.
// It resets Color, Host, and Title, then updates them based on query values.
// Returns an error if the resolver fails to set any parameter.
func (config *Config) setQueryParams(resolver types.ConfigQueryResolver, query url.Values) error {
	config.Color = ""
	config.Host = ""
	config.Title = ""

	for key, vals := range query {
		if len(vals) > 0 && vals[0] != "" {
			switch key {
			case "color":
				config.Color = vals[0]
			case "host":
				config.Host = vals[0]
			case "title":
				config.Title = vals[0]
			}

			if err := resolver.Set(key, vals[0]); err != nil {
				return fmt.Errorf(
					"%w: key=%q, value=%q: %w",
					ErrSetParameterFailed,
					key,
					vals[0],
					err,
				)
			}
		}
	}

	return nil
}

// setFromWebhookParts sets Config fields from webhook parts.
func (config *Config) setFromWebhookParts(parts [5]string) {
	config.Group = parts[0]
	config.Tenant = parts[1]
	config.AltID = parts[2]
	config.GroupOwner = parts[3]
	config.ExtraID = parts[4]
}
