// Copyright IBM Corp. 2018, 2025
// SPDX-License-Identifier: MPL-2.0

package tfe

import (
	"context"
	"fmt"
	"net/url"
	"time"
)

type StackDiagnostics interface {
	// Read retrieves a stack diagnostic by its ID.
	Read(ctx context.Context, stackConfigurationID string) (*StackDiagnostic, error)
	// Acknowledge marks a diagnostic as acknowledged.
	Acknowledge(ctx context.Context, stackDiagnosticID string) error
}

// StackDiagnostic represents any sourcebundle.Diagnostic value. The simplest form has
// just a severity, single line summary, and optional detail. If there is more
// information about the source of the diagnostic, this is represented in the
// range field.
type StackDiagnostic struct {
	ID             string                    `jsonapi:"primary,stack-diagnostics"`
	Severity       string                    `jsonapi:"attr,severity"`
	Summary        string                    `jsonapi:"attr,summary"`
	Detail         string                    `jsonapi:"attr,detail"`
	Diags          []*StackDiagnosticSummary `jsonapi:"attr,diags"`
	Acknowledged   bool                      `jsonapi:"attr,acknowledged"`
	AcknowledgedAt *time.Time                `jsonapi:"attr,acknowledged-at,iso8601"`
	CreatedAt      *time.Time                `jsonapi:"attr,created-at,iso8601"`

	// Relationships
	StackDeploymentStep *StackDeploymentStep `jsonapi:"relation,stack-deployment-step"`
	StackConfiguration  *StackConfiguration  `jsonapi:"relation,stack-configuration"`
	AcknowledgedBy      *User                `jsonapi:"relation,acknowledged-by"`
}

type StackDiagnosticSummary struct {
	Severity string             `jsonapi:"attr,severity"`
	Summary  string             `jsonapi:"attr,summary"`
	Detail   string             `jsonapi:"attr,detail"`
	Range    *DiagnosticRange   `jsonapi:"attr,range"`
	Origin   string             `jsonapi:"attr,origin"`
	Snippet  *DiagnosticSnippet `jsonapi:"attr,snippet"`
}

type DiagnosticSnippet struct {
	Code                 string   `jsonapi:"attr,code"`
	Values               []string `jsonapi:"attr,values"`
	Context              *string  `jsonapi:"attr,context"`
	StartLine            int      `jsonapi:"attr,start_line"`
	HighlightEndOffset   int      `jsonapi:"attr,highlight_end_offset"`
	HighlightStartOffset int      `jsonapi:"attr,highlight_start_offset"`
}

type stackDiagnostics struct {
	client *Client
}

type StackDiagnosticsList struct {
	Items []*StackDiagnostic
}

// DiagnosticPos represents a position in the source code.
type DiagnosticPos struct {
	// Line is a one-based count for the line in the indicated file.
	Line int `jsonapi:"attr,line"`

	// Column is a one-based count of Unicode characters from the start of the line.
	Column int `jsonapi:"attr,column"`

	// Byte is a zero-based offset into the indicated file.
	Byte int `jsonapi:"attr,byte"`
}

// DiagnosticRange represents the filename and position of the diagnostic
// subject. This defines the range of the source to be highlighted in the
// output. Note that the snippet may include additional surrounding source code
// if the diagnostic has a context range.
//
// The stacks-specific source field represents the full source bundle address
// of the file, while the filename field is the sub path relative to its
// enclosing package. This represents an attempt to be somewhat backwards
// compatible with the existing Terraform JSON diagnostic format, where
// filename is root module relative.
//
// The Start position is inclusive, and the End position is exclusive. Exact
// positions are intended for highlighting for human interpretation only and
// are subject to change.
type DiagnosticRange struct {
	Filename string        `jsonapi:"attr,filename"`
	Source   string        `jsonapi:"attr,source"`
	Start    DiagnosticPos `jsonapi:"attr,start"`
	End      DiagnosticPos `jsonapi:"attr,end"`
}

// Read retrieves a stack diagnostic by its ID.
func (s stackDiagnostics) Read(ctx context.Context, stackDiagnosticID string) (*StackDiagnostic, error) {
	req, err := s.client.NewRequest("GET", fmt.Sprintf("stack-diagnostics/%s", url.PathEscape(stackDiagnosticID)), nil)
	if err != nil {
		return nil, err
	}

	var diagnostics StackDiagnostic
	if err := req.Do(ctx, &diagnostics); err != nil {
		return nil, err
	}

	return &diagnostics, nil
}

// Acknowledge marks a diagnostic as acknowledged.
func (s stackDiagnostics) Acknowledge(ctx context.Context, stackDiagnosticID string) error {
	req, err := s.client.NewRequest("POST", fmt.Sprintf("stack-diagnostics/%s/acknowledge", url.PathEscape(stackDiagnosticID)), nil)
	if err != nil {
		return err
	}

	diagnostic := StackDiagnostic{}
	if err := req.Do(ctx, &diagnostic); err != nil {
		return err
	}

	return nil
}
