package audit

import (
	"strings"
	"time"
)

const VerificationNotApplicable = "not_applicable"

type ReplicationEvidence struct {
	Manifest                      BatchManifest `json:"manifest"`
	ReplicationURI                string        `json:"replication_uri"`
	SignerKeyID                   string        `json:"signer_key_id"`
	ReplicatedAt                  time.Time     `json:"replicated_at"`
	RetentionProfile              string        `json:"retention_profile"`
	WORMRetentionMode              string        `json:"worm_retention_mode"`
	WORMRetentionUntil             *time.Time    `json:"worm_retention_until"`
	RetentionPolicyEvidenceURI     string        `json:"retention_policy_evidence_uri"`
	SeparationOfDutiesEvidenceURI  string        `json:"separation_of_duties_evidence_uri"`
	AlertCoverage                  []string      `json:"alert_coverage"`
}

type ReplicationGateOptions struct {
	Now                 time.Time
	MaxReplicationLag   time.Duration
	RequireWORM         bool
}

type ReplicationGateStatus struct {
	Result                    string     `json:"result"`
	EnvironmentProfile        string     `json:"environment_profile"`
	BatchID                   string     `json:"batch_id"`
	BatchSequence             int64      `json:"batch_sequence"`
	ReplicationURI            string     `json:"replication_uri"`
	SignerKeyID               string     `json:"signer_key_id"`
	ReplicationFreshness      string     `json:"replication_freshness"`
	ReplicationFreshnessAge   int64      `json:"replication_freshness_age_seconds"`
	RetentionProfile          string     `json:"retention_profile,omitempty"`
	WORMRetentionStatus       string     `json:"worm_retention_status"`
	WORMRetentionUntil        *time.Time  `json:"worm_retention_until,omitempty"`
	SeparationOfDutiesStatus  string     `json:"separation_of_duties_status"`
	AlertCoverageStatus       string     `json:"alert_coverage_status"`
	Findings                  []string   `json:"findings"`
}

func BuildReplicationGateStatus(evidence ReplicationEvidence, options ReplicationGateOptions) ReplicationGateStatus {
	now := normalizeAuditIntegrityTime(options.Now)
	if now.IsZero() {
		now = normalizeAuditIntegrityTime(time.Now())
	}
	status := ReplicationGateStatus{
		Result:                   VerificationPass,
		EnvironmentProfile:       strings.TrimSpace(evidence.Manifest.EnvironmentProfile),
		BatchID:                  strings.TrimSpace(evidence.Manifest.BatchID),
		BatchSequence:            evidence.Manifest.SequenceNumber,
		ReplicationURI:           strings.TrimSpace(evidence.ReplicationURI),
		SignerKeyID:              strings.TrimSpace(evidence.SignerKeyID),
		ReplicationFreshness:     VerificationPass,
		WORMRetentionStatus:      VerificationNotApplicable,
		SeparationOfDutiesStatus: VerificationNotApplicable,
		AlertCoverageStatus:      VerificationPass,
		RetentionProfile:         strings.TrimSpace(evidence.RetentionProfile),
	}
	if evidence.WORMRetentionUntil != nil {
		retentionUntil := normalizeAuditIntegrityTime(*evidence.WORMRetentionUntil)
		status.WORMRetentionUntil = &retentionUntil
	}

	if err := VerifyBatchManifest(evidence.Manifest); err != nil {
		status.addFinding(VerificationFail, "batch manifest verification failed: "+err.Error())
	}
	if status.ReplicationURI == "" {
		status.addFinding(VerificationBlocked, "replication_uri is required")
	} else if !isExternalAuditReplicationURI(status.ReplicationURI) {
		status.addFinding(VerificationBlocked, "replication_uri must identify storage outside the primary database control plane")
	}
	if status.SignerKeyID == "" {
		status.addFinding(VerificationBlocked, "signer_key_id is required")
	}
	replicatedAt := normalizeAuditIntegrityTime(evidence.ReplicatedAt)
	if replicatedAt.IsZero() {
		status.ReplicationFreshness = VerificationBlocked
		status.addFinding(VerificationBlocked, "replicated_at is required")
	} else {
		status.ReplicationFreshnessAge = int64(now.Sub(replicatedAt).Seconds())
		if replicatedAt.Before(normalizeAuditIntegrityTime(evidence.Manifest.CreatedAt)) {
			status.ReplicationFreshness = VerificationFail
			status.addFinding(VerificationFail, "replicated_at is before manifest created_at")
		}
		if options.MaxReplicationLag > 0 && now.Sub(replicatedAt) > options.MaxReplicationLag {
			status.ReplicationFreshness = VerificationFail
			status.addFinding(VerificationFail, "replication freshness exceeds threshold")
		}
	}

	wormClaimed := options.RequireWORM || strings.TrimSpace(evidence.WORMRetentionMode) != "" || evidence.WORMRetentionUntil != nil || strings.TrimSpace(evidence.RetentionProfile) != ""
	if wormClaimed {
		status.WORMRetentionStatus = VerificationPass
		status.SeparationOfDutiesStatus = VerificationPass
		mode := strings.TrimSpace(evidence.WORMRetentionMode)
		if status.RetentionProfile == "" {
			status.WORMRetentionStatus = VerificationBlocked
			status.addFinding(VerificationBlocked, "retention_profile is required for WORM/Object Lock claims")
		}
		if mode != "governance" && mode != "compliance" {
			status.WORMRetentionStatus = VerificationBlocked
			status.addFinding(VerificationBlocked, "worm_retention_mode must be governance or compliance")
		}
		if status.WORMRetentionUntil == nil || !status.WORMRetentionUntil.After(now) {
			status.WORMRetentionStatus = VerificationBlocked
			status.addFinding(VerificationBlocked, "worm_retention_until must be in the future")
		}
		if strings.TrimSpace(evidence.RetentionPolicyEvidenceURI) == "" {
			status.WORMRetentionStatus = VerificationBlocked
			status.addFinding(VerificationBlocked, "retention_policy_evidence_uri is required")
		}
		if strings.TrimSpace(evidence.SeparationOfDutiesEvidenceURI) == "" {
			status.SeparationOfDutiesStatus = VerificationBlocked
			status.addFinding(VerificationBlocked, "separation_of_duties_evidence_uri is required")
		}
	}

	if missing := missingAuditAlertCoverage(evidence.AlertCoverage); len(missing) > 0 {
		status.AlertCoverageStatus = VerificationBlocked
		status.addFinding(VerificationBlocked, "missing alert coverage: "+strings.Join(missing, ", "))
	}
	return status
}

func (status *ReplicationGateStatus) addFinding(result string, finding string) {
	status.Findings = append(status.Findings, finding)
	if result == VerificationFail {
		status.Result = VerificationFail
		return
	}
	if status.Result != VerificationFail {
		status.Result = VerificationBlocked
	}
}

func isExternalAuditReplicationURI(uri string) bool {
	normalized := strings.ToLower(strings.TrimSpace(uri))
	if normalized == "" {
		return false
	}
	for _, prefix := range []string{"postgres://", "postgresql://", "db://", "file://"} {
		if strings.HasPrefix(normalized, prefix) {
			return false
		}
	}
	for _, prefix := range []string{"s3://", "gs://", "azblob://", "security-lake://", "https://"} {
		if strings.HasPrefix(normalized, prefix) {
			return true
		}
	}
	return false
}

func missingAuditAlertCoverage(alerts []string) []string {
	required := map[string]bool{
		"audit.replication.lag": false,
		"audit.worm.retention":  false,
		"audit.control.change":  false,
	}
	for _, alert := range alerts {
		key := strings.TrimSpace(alert)
		if _, ok := required[key]; ok {
			required[key] = true
		}
	}
	missing := make([]string, 0, len(required))
	for key, present := range required {
		if !present {
			missing = append(missing, key)
		}
	}
	return missing
}
