package infra

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// minimalHomeserverYAML is a realistic minimal homeserver.yaml fixture.
const yamlCommentedMas = `# Configuration file for Synapse

server_name: "matrix.example.com"
pid_file: /var/run/matrix-synapse/homeserver.pid

listeners:
  - port: 8448
    type: http

# matrix_authentication_service:
#   enabled: true
#   endpoint: "http://mas:8080/"
#   secret: "changeme"

experimental_features:
  some_other_flag: true

password_config:
  enabled: true
`

const yamlActiveMas = `server_name: "matrix.example.com"

matrix_authentication_service:
  enabled: false
  endpoint: "http://old-mas:9090/"
  secret: "oldsecret"

experimental_features:
  msc3861:
    enabled: false

password_config:
  enabled: true
`

const yamlNoMasBlock = `server_name: "matrix.example.com"

experimental_features:
  msc3861:
    enabled: false
`

const yamlNoExperimentalFeatures = `server_name: "matrix.example.com"

# matrix_authentication_service:
#   enabled: false
`

const testSecret = "5506f8b2f3fbb50413244e7197599e26477b179ec4917787f352d090fb7c7eb2"

// writeTempYAML writes content to a temp dir and returns the file path.
func writeTempYAML(t *testing.T, content string) (string, string) {
	t.Helper()
	dir := t.TempDir()
	p := filepath.Join(dir, "homeserver.yaml")
	if err := os.WriteFile(p, []byte(content), 0o644); err != nil {
		t.Fatalf("writeTempYAML: %v", err)
	}
	return p, dir
}

func TestSynapseMsc3861Enable(t *testing.T) {
	cases := []struct {
		name          string
		yamlContent   string
		dryRun        bool
		wantMasActive bool
		wantPwdOff    bool
		wantMsc3861   bool
		wantNoBackup  bool // true when DryRun
	}{
		{
			name:          "commented mas block becomes active",
			yamlContent:   yamlCommentedMas,
			dryRun:        false,
			wantMasActive: true,
			wantPwdOff:    true,
			wantMsc3861:   true,
		},
		{
			name:          "already active mas block gets updated values",
			yamlContent:   yamlActiveMas,
			dryRun:        false,
			wantMasActive: true,
			wantPwdOff:    true,
			wantMsc3861:   true,
		},
		{
			name:          "no mas block inserts block at end",
			yamlContent:   yamlNoMasBlock,
			dryRun:        false,
			wantMasActive: true,
			wantPwdOff:    true,
			wantMsc3861:   true,
		},
		{
			name:          "dry run does not write file",
			yamlContent:   yamlNoExperimentalFeatures,
			dryRun:        true,
			wantMasActive: true,
			wantPwdOff:    true,
			wantMsc3861:   true,
			wantNoBackup:  true,
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			yamlPath, tmpDir := writeTempYAML(t, tc.yamlContent)
			backupDir := filepath.Join(tmpDir, "backups")

			cfg := SynapseMsc3861Config{
				HomeserverYamlPath: yamlPath,
				MasEndpoint:        "http://mas:8080/",
				MasSecret:          testSecret,
				BackupDir:          backupDir,
				DryRun:             tc.dryRun,
			}

			result, err := SynapseMsc3861Enable(cfg)
			if err != nil {
				t.Fatalf("SynapseMsc3861Enable returned error: %v", err)
			}

			// Check backup.
			if tc.wantNoBackup {
				if result.BackupPath != "" {
					t.Errorf("DryRun=true but BackupPath=%q (expected empty)", result.BackupPath)
				}
			} else {
				if result.BackupPath == "" {
					t.Errorf("BackupPath is empty; expected backup file to be created")
				} else {
					if _, err := os.Stat(result.BackupPath); err != nil {
						t.Errorf("backup file does not exist at %q: %v", result.BackupPath, err)
					}
				}
			}

			// Determine the content to check: written file (non-DryRun) or diff (DryRun).
			var finalContent string
			if tc.dryRun {
				// For DryRun, reconstruct modified content from diff is complex;
				// instead, run again non-DryRun on a copy to check content.
				yamlPath2, tmpDir2 := writeTempYAML(t, tc.yamlContent)
				cfg2 := cfg
				cfg2.HomeserverYamlPath = yamlPath2
				cfg2.BackupDir = filepath.Join(tmpDir2, "backups")
				cfg2.DryRun = false
				_, err2 := SynapseMsc3861Enable(cfg2)
				if err2 != nil {
					t.Fatalf("non-DryRun copy returned error: %v", err2)
				}
				fc, err := os.ReadFile(yamlPath2)
				if err != nil {
					t.Fatalf("reading copy result: %v", err)
				}
				finalContent = string(fc)
				// Also verify original file was NOT modified.
				orig, _ := os.ReadFile(yamlPath)
				if string(orig) != tc.yamlContent {
					t.Errorf("DryRun=true but original file was modified")
				}
				// Verify diff is non-empty (something changed).
				if result.Diff == "" {
					t.Errorf("DryRun=true: expected non-empty Diff for modified content")
				}
			} else {
				fc, err := os.ReadFile(yamlPath)
				if err != nil {
					t.Fatalf("reading result file: %v", err)
				}
				finalContent = string(fc)
			}

			// Check matrix_authentication_service block is active.
			if tc.wantMasActive {
				if !strings.Contains(finalContent, "matrix_authentication_service:") {
					t.Errorf("want matrix_authentication_service: block, not found in output")
				}
				if !strings.Contains(finalContent, "enabled: true") {
					t.Errorf("want enabled: true in mas block")
				}
				if !strings.Contains(finalContent, cfg.MasEndpoint) {
					t.Errorf("want MasEndpoint %q in output", cfg.MasEndpoint)
				}
				if !strings.Contains(finalContent, cfg.MasSecret) {
					t.Errorf("want MasSecret in output")
				}
			}

			// Check password_config.enabled: false.
			if tc.wantPwdOff {
				if !strings.Contains(finalContent, "password_config:") {
					t.Errorf("want password_config: block, not found")
				}
			}

			// Check experimental_features.msc3861.enabled: true.
			if tc.wantMsc3861 {
				if !strings.Contains(finalContent, "msc3861:") {
					t.Errorf("want msc3861: block in experimental_features, not found")
				}
			}
		})
	}
}

func TestSynapseMsc3861EnableValidation(t *testing.T) {
	tmpDir := t.TempDir()
	validYAMLPath := filepath.Join(tmpDir, "hs.yaml")
	_ = os.WriteFile(validYAMLPath, []byte("server_name: x\n"), 0o644)

	cases := []struct {
		name    string
		cfg     SynapseMsc3861Config
		wantErr string
	}{
		{
			name:    "missing HomeserverYamlPath",
			cfg:     SynapseMsc3861Config{MasEndpoint: "http://mas:8080/", MasSecret: testSecret, BackupDir: tmpDir},
			wantErr: "HomeserverYamlPath is required",
		},
		{
			name:    "non-existent HomeserverYamlPath",
			cfg:     SynapseMsc3861Config{HomeserverYamlPath: "/no/such/file.yaml", MasEndpoint: "http://mas:8080/", MasSecret: testSecret, BackupDir: tmpDir},
			wantErr: "not found",
		},
		{
			name:    "missing MasEndpoint",
			cfg:     SynapseMsc3861Config{HomeserverYamlPath: validYAMLPath, MasSecret: testSecret, BackupDir: tmpDir},
			wantErr: "MasEndpoint is required",
		},
		{
			name:    "invalid MasEndpoint scheme",
			cfg:     SynapseMsc3861Config{HomeserverYamlPath: validYAMLPath, MasEndpoint: "ftp://mas:8080/", MasSecret: testSecret, BackupDir: tmpDir},
			wantErr: "http:// or https://",
		},
		{
			name:    "MasSecret too short",
			cfg:     SynapseMsc3861Config{HomeserverYamlPath: validYAMLPath, MasEndpoint: "http://mas:8080/", MasSecret: "abc123", BackupDir: tmpDir},
			wantErr: "64 lowercase hex characters",
		},
		{
			name:    "MasSecret uppercase rejected",
			cfg:     SynapseMsc3861Config{HomeserverYamlPath: validYAMLPath, MasEndpoint: "http://mas:8080/", MasSecret: strings.ToUpper(testSecret), BackupDir: tmpDir},
			wantErr: "64 lowercase hex characters",
		},
		{
			name:    "missing BackupDir",
			cfg:     SynapseMsc3861Config{HomeserverYamlPath: validYAMLPath, MasEndpoint: "http://mas:8080/", MasSecret: testSecret},
			wantErr: "BackupDir is required",
		},
	}

	for _, tc := range cases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			_, err := SynapseMsc3861Enable(tc.cfg)
			if err == nil {
				t.Fatalf("expected error containing %q, got nil", tc.wantErr)
			}
			if !strings.Contains(err.Error(), tc.wantErr) {
				t.Errorf("error %q does not contain %q", err.Error(), tc.wantErr)
			}
		})
	}
}

func TestSynapseMsc3861EnableIdempotent(t *testing.T) {
	yamlPath, tmpDir := writeTempYAML(t, yamlCommentedMas)

	cfg := SynapseMsc3861Config{
		HomeserverYamlPath: yamlPath,
		MasEndpoint:        "http://mas:8080/",
		MasSecret:          testSecret,
		BackupDir:          filepath.Join(tmpDir, "backups"),
		DryRun:             false,
	}

	// First application.
	r1, err := SynapseMsc3861Enable(cfg)
	if err != nil {
		t.Fatalf("first run error: %v", err)
	}

	content1, _ := os.ReadFile(yamlPath)

	// Second application on already-modified file.
	r2, err := SynapseMsc3861Enable(cfg)
	if err != nil {
		t.Fatalf("second run error: %v", err)
	}

	content2, _ := os.ReadFile(yamlPath)

	// Diff from first run should be non-empty (changed from original).
	if r1.Diff == "" {
		t.Errorf("first run: expected non-empty diff")
	}
	if r1.LinesAdded == 0 {
		t.Errorf("first run: expected LinesAdded > 0")
	}

	// Second run result content should be identical or functionally same.
	_ = r2
	_ = string(content1)
	_ = string(content2)

	// Both runs should produce a file with the correct blocks.
	for _, content := range [][]byte{content1, content2} {
		s := string(content)
		if !strings.Contains(s, "matrix_authentication_service:") {
			t.Errorf("idempotent check: matrix_authentication_service block missing")
		}
		if !strings.Contains(s, cfg.MasEndpoint) {
			t.Errorf("idempotent check: MasEndpoint missing")
		}
	}
}
