package steme

import (
	"context"
	"fmt"
)

// AphoriaClient provides access to Aphoria code-level truth linting endpoints.
//
// Aphoria scans codebases, extracts decisions embedded in config and code,
// and checks them against authoritative sources (RFCs, OWASP, etc.).
type AphoriaClient struct {
	client *Client
}

// Aphoria returns a client for Aphoria operations.
//
// Example:
//
//	result, err := client.Aphoria().Scan(ctx, ScanParams{
//	    TargetPath: "/path/to/project",
//	})
func (c *Client) Aphoria() *AphoriaClient {
	return &AphoriaClient{client: c}
}

// BlessParams defines parameters for blessing a code pattern.
type BlessParams struct {
	// ConceptPath to bless (e.g., "code://rust/grpc/tls").
	ConceptPath string `json:"concept_path"`

	// Predicate being defined (e.g., "enabled", "min_version").
	Predicate string `json:"predicate"`

	// Value for this standard (e.g., "true", "1.2").
	Value string `json:"value"`

	// Reason/description for why this is the standard.
	Reason string `json:"reason"`
}

// BlessResult represents the response from blessing a pattern.
type BlessResult struct {
	Success     bool   `json:"success"`
	Message     string `json:"message"`
	ConceptPath string `json:"concept_path"`
}

// Bless marks a code pattern as the authoritative standard.
//
// Unlike acknowledge (which creates a suppression), bless creates an
// assertion with the actual predicate and value that becomes the
// authoritative standard for future scans.
//
// Example:
//
//	result, err := client.Aphoria().Bless(ctx, BlessParams{
//	    ConceptPath: "code://rust/grpc/tls",
//	    Predicate:   "enabled",
//	    Value:       "true",
//	    Reason:      "All services MUST use mTLS",
//	})
func (a *AphoriaClient) Bless(ctx context.Context, params BlessParams) (*BlessResult, error) {
	var result BlessResult
	if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/bless", params, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ExportPolicyParams defines parameters for exporting a Trust Pack.
type ExportPolicyParams struct {
	// Name for the Trust Pack (e.g., "acme-security-policy").
	Name string `json:"name"`

	// Description for the pack (optional).
	Description string `json:"description,omitempty"`

	// OutputPath where the pack should be saved (optional).
	// If not provided, uses default name based on pack name.
	OutputPath string `json:"output_path,omitempty"`
}

// ExportPolicyResult represents the response from exporting a policy.
type ExportPolicyResult struct {
	// PackPath where the pack was saved.
	PackPath string `json:"pack_path,omitempty"`

	// Number of assertions exported.
	AssertionsCount int `json:"assertions_count"`

	// Number of aliases exported.
	AliasesCount int `json:"aliases_count"`

	// Hex-encoded issuer public key (first 8 chars).
	IssuerHex string `json:"issuer_hex"`

	// Name of the exported pack.
	PackName string `json:"pack_name"`

	// Version of the exported pack.
	PackVersion string `json:"pack_version"`
}

// ExportPolicy exports policy assertions as a Trust Pack.
//
// Collects all acknowledged conflicts and manual aliases into a signed
// Trust Pack that can be shared with other projects.
//
// Example:
//
//	result, err := client.Aphoria().ExportPolicy(ctx, ExportPolicyParams{
//	    Name:        "acme-security",
//	    Description: "Acme Corp security standards",
//	    OutputPath:  "./acme-security.pack",
//	})
func (a *AphoriaClient) ExportPolicy(ctx context.Context, params ExportPolicyParams) (*ExportPolicyResult, error) {
	var result ExportPolicyResult
	if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/policy/export", params, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ImportPolicyParams defines parameters for importing a Trust Pack.
type ImportPolicyParams struct {
	// PackPath to the Trust Pack file.
	PackPath string `json:"pack_path"`
}

// ImportPolicyResult represents the response from importing a policy.
type ImportPolicyResult struct {
	Success bool `json:"success"`

	// Number of assertions imported.
	AssertionsImported int `json:"assertions_imported"`

	// Number of aliases imported.
	AliasesImported int `json:"aliases_imported"`

	// Name of the imported pack.
	PackName string `json:"pack_name"`

	// Version of the imported pack.
	PackVersion string `json:"pack_version"`

	// Hex-encoded issuer public key (first 8 chars).
	IssuerHex string `json:"issuer_hex"`
}

// ImportPolicy imports a Trust Pack into the local Episteme.
//
// Loads and verifies the pack's signature, then imports assertions
// and aliases into the local storage.
//
// Example:
//
//	result, err := client.Aphoria().ImportPolicy(ctx, ImportPolicyParams{
//	    PackPath: "./acme-security.pack",
//	})
func (a *AphoriaClient) ImportPolicy(ctx context.Context, params ImportPolicyParams) (*ImportPolicyResult, error) {
	var result ImportPolicyResult
	if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/policy/import", params, &result); err != nil {
		return nil, err
	}
	return &result, nil
}

// ScanParams defines parameters for scanning a project.
type ScanParams struct {
	// TargetPath to the project root to scan.
	TargetPath string `json:"target_path"`

	// Format for output: "table", "json", "sarif", "markdown".
	// Defaults to "json".
	Format string `json:"format,omitempty"`

	// FailOnFlag returns 422 status code if BLOCK findings exist.
	FailOnFlag bool `json:"fail_on_flag,omitempty"`

	// MinSeverity to report: "pass", "flag", "block".
	MinSeverity string `json:"min_severity,omitempty"`

	// Debug enables conflict resolution traces in the output.
	Debug bool `json:"debug,omitempty"`
}

// ScanResult represents the response from a scan operation.
type ScanResult struct {
	// Project name.
	Project string `json:"project"`

	// Unique scan ID.
	ScanID string `json:"scan_id"`

	// Number of files scanned.
	FilesScanned int `json:"files_scanned"`

	// Number of claims extracted.
	ClaimsExtracted int `json:"claims_extracted"`

	// Findings (conflicts detected).
	Findings []Finding `json:"findings"`

	// Summary counts by verdict.
	Summary ScanSummary `json:"summary"`
}

// HasBlocks returns true if any findings have BLOCK verdict.
func (r *ScanResult) HasBlocks() bool {
	return r.Summary.Blocked > 0
}

// HasFlags returns true if any findings have FLAG verdict.
func (r *ScanResult) HasFlags() bool {
	return r.Summary.Flagged > 0
}

// Finding represents a single finding from the scan.
type Finding struct {
	// ConceptPath of the claim.
	ConceptPath string `json:"concept_path"`

	// Predicate being claimed.
	Predicate string `json:"predicate"`

	// Value found in code.
	CodeValue string `json:"code_value"`

	// Source file path.
	File string `json:"file"`

	// Line number in the source file.
	Line int `json:"line"`

	// ConflictScore (0.0 to 1.0).
	ConflictScore float64 `json:"conflict_score"`

	// Verdict: "BLOCK", "FLAG", "PASS", "ACK".
	Verdict string `json:"verdict"`

	// Conflicts with authoritative sources.
	Conflicts []ConflictingSource `json:"conflicts"`

	// Acknowledgment info if this conflict was acknowledged.
	Acknowledgment *Acknowledgment `json:"acknowledgment,omitempty"`

	// Debug trace (if debug mode enabled).
	Trace *ConflictTrace `json:"trace,omitempty"`
}

// IsBlocked returns true if this finding has BLOCK verdict.
func (f *Finding) IsBlocked() bool {
	return f.Verdict == "BLOCK"
}

// IsFlagged returns true if this finding has FLAG verdict.
func (f *Finding) IsFlagged() bool {
	return f.Verdict == "FLAG"
}

// ConflictingSource represents a source that conflicts with a code claim.
type ConflictingSource struct {
	// Path of the authoritative source.
	Path string `json:"path"`

	// SourceClass (tier name).
	SourceClass string `json:"source_class"`

	// Authoritative value.
	Value string `json:"value"`

	// RFC/OWASP citation if applicable.
	Citation string `json:"citation,omitempty"`

	// PolicySource info if from a Trust Pack.
	PolicySource *PolicySource `json:"policy_source,omitempty"`
}

// PolicySource contains information about a Trust Pack.
type PolicySource struct {
	PackName    string `json:"pack_name"`
	PackVersion string `json:"pack_version"`
	IssuerHex   string `json:"issuer_hex"`
}

// Acknowledgment contains information about a conflict acknowledgment.
type Acknowledgment struct {
	Timestamp string `json:"timestamp"`
	By        string `json:"by"`
	Reason    string `json:"reason"`
}

// ConflictTrace contains debug information about conflict resolution.
type ConflictTrace struct {
	CodeClaim      string `json:"code_claim"`
	AuthorityMatch string `json:"authority_match"`
	AuthorityTier  string `json:"authority_tier"`
	Resolution     string `json:"resolution"`
}

// ScanSummary contains counts by verdict.
type ScanSummary struct {
	Total        int `json:"total"`
	Blocked      int `json:"blocked"`
	Flagged      int `json:"flagged"`
	Passed       int `json:"passed"`
	Acknowledged int `json:"acknowledged"`
}

// Scan runs a scan on a project for conflicts.
//
// Scans the specified project directory, extracts claims from code/config,
// and checks them against authoritative sources.
//
// When FailOnFlag is true and BLOCK findings exist, the API returns 422
// and this method returns an APIError with StatusCode 422. The result
// is still accessible via the error.
//
// Example:
//
//	result, err := client.Aphoria().Scan(ctx, ScanParams{
//	    TargetPath: "/path/to/project",
//	    Format:     "json",
//	    FailOnFlag: true,
//	})
//	if err != nil {
//	    if apiErr, ok := err.(*APIError); ok && apiErr.StatusCode == 422 {
//	        // Scan succeeded but found blocking issues
//	        fmt.Printf("Found %d blocking issues\n", result.Summary.Blocked)
//	    }
//	}
func (a *AphoriaClient) Scan(ctx context.Context, params ScanParams) (*ScanResult, error) {
	if params.Format == "" {
		params.Format = "json"
	}

	var result ScanResult
	if err := a.client.doJSON(ctx, "POST", "/v1/aphoria/scan", params, &result); err != nil {
		// For 422, we still want to return the result if possible
		if apiErr, ok := err.(*APIError); ok && apiErr.StatusCode == 422 {
			// The response body may contain the scan result
			return &result, fmt.Errorf("scan found blocking issues: %w", err)
		}
		return nil, err
	}

	return &result, nil
}
