package infra

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"
)

// WellknownOidcPatchConfig holds the parameters for WellknownOidcPatch.
type WellknownOidcPatchConfig struct {
	WellknownJsonPath string // absolute path to the .well-known/matrix/client JSON file
	Issuer            string // MAS issuer URL, must end with "/" (RFC 8414)
	AccountURL        string // MAS account page URL
	BackupDir         string // directory where the backup file is written
	DryRun            bool   // if true, return Before/After without writing
}

// WellknownOidcPatchResult is returned by WellknownOidcPatch.
type WellknownOidcPatchResult struct {
	BackupPath string // path of the backup file; empty on DryRun
	Before     string // original JSON (pretty-printed, 2-space indent)
	After      string // patched JSON (pretty-printed, 2-space indent)
	Modified   bool   // false if the key already existed with identical values
}

// WellknownOidcPatch reads a Matrix .well-known/matrix/client JSON file,
// adds (or updates) the org.matrix.msc2965.authentication key with the
// supplied MAS issuer and account URL, and writes the result back to the
// same path. All existing keys (m.homeserver, org.matrix.msc4143.rtc_foci,
// etc.) are preserved. A timestamped backup is created in BackupDir before
// any write. Set DryRun to true to preview the change without touching the
// filesystem.
func WellknownOidcPatch(cfg WellknownOidcPatchConfig) (WellknownOidcPatchResult, error) {
	const oidcKey = "org.matrix.msc2965.authentication"

	// 1. Read existing file.
	raw, err := os.ReadFile(cfg.WellknownJsonPath)
	if err != nil {
		return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: read %s: %w", cfg.WellknownJsonPath, err)
	}

	// 2. Parse into a generic map to preserve unknown keys.
	var doc map[string]any
	if err := json.Unmarshal(raw, &doc); err != nil {
		return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: invalid JSON in %s: %w", cfg.WellknownJsonPath, err)
	}

	// 3. Pretty-print Before.
	beforeBytes, err := json.MarshalIndent(doc, "", "  ")
	if err != nil {
		return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: marshal before: %w", err)
	}
	before := string(beforeBytes)

	// 4. Build the new authentication block.
	newAuth := map[string]any{
		"issuer":  cfg.Issuer,
		"account": cfg.AccountURL,
	}

	// 5. Check if the key already exists with identical values.
	modified := true
	if existing, ok := doc[oidcKey]; ok {
		existingBytes, _ := json.Marshal(existing)
		newBytes, _ := json.Marshal(newAuth)
		if string(existingBytes) == string(newBytes) {
			modified = false
		}
	}

	if !modified {
		return WellknownOidcPatchResult{
			BackupPath: "",
			Before:     before,
			After:      before,
			Modified:   false,
		}, nil
	}

	// 6. Apply the patch.
	doc[oidcKey] = newAuth

	afterBytes, err := json.MarshalIndent(doc, "", "  ")
	if err != nil {
		return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: marshal after: %w", err)
	}
	after := string(afterBytes)

	// 7. DryRun: return without writing anything.
	if cfg.DryRun {
		return WellknownOidcPatchResult{
			BackupPath: "",
			Before:     before,
			After:      after,
			Modified:   true,
		}, nil
	}

	// 8. Create backup.
	if err := os.MkdirAll(cfg.BackupDir, 0o755); err != nil {
		return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: mkdir backup dir: %w", err)
	}
	backupName := fmt.Sprintf("wellknown_%d.json", time.Now().Unix())
	backupPath := filepath.Join(cfg.BackupDir, backupName)
	if err := os.WriteFile(backupPath, raw, 0o644); err != nil {
		return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: write backup: %w", err)
	}

	// 9. Write patched file.
	if err := os.WriteFile(cfg.WellknownJsonPath, afterBytes, 0o644); err != nil {
		return WellknownOidcPatchResult{}, fmt.Errorf("wellknown_oidc_patch: write %s: %w", cfg.WellknownJsonPath, err)
	}

	return WellknownOidcPatchResult{
		BackupPath: backupPath,
		Before:     before,
		After:      after,
		Modified:   true,
	}, nil
}
