package infra

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// whoamiHandler devuelve un handler httptest que simula /_matrix/client/v3/account/whoami.
// Si statusCode != 200, devuelve un RespError de mautrix.
func whoamiHandler(t *testing.T, statusCode int, userID, deviceID string) http.HandlerFunc {
	t.Helper()
	return func(w http.ResponseWriter, r *http.Request) {
		if statusCode != http.StatusOK {
			// mautrix espera JSON con errcode/error para errores Matrix
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(statusCode)
			_ = json.NewEncoder(w).Encode(map[string]string{
				"errcode": "M_UNKNOWN_TOKEN",
				"error":   "Invalid macaroon passed.",
			})
			return
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(map[string]string{
			"user_id":   userID,
			"device_id": deviceID,
		})
	}
}

func TestMatrixClientInit(t *testing.T) {
	t.Run("HomeserverURL invalido", func(t *testing.T) {
		tmpDir := t.TempDir()
		cfg := MatrixClientInitConfig{
			HomeserverURL: "not-a-url",
			UserID:        "@user:server",
			AccessToken:   "mxat_test",
			StoreDir:      tmpDir,
		}
		_, err := MatrixClientInit(cfg)
		if err == nil {
			t.Fatal("esperaba error con HomeserverURL invalido, got nil")
		}
		if !strings.Contains(err.Error(), "HomeserverURL") {
			t.Errorf("error deberia mencionar HomeserverURL, got: %v", err)
		}
	})

	t.Run("UserID format invalido", func(t *testing.T) {
		tmpDir := t.TempDir()
		cfg := MatrixClientInitConfig{
			HomeserverURL: "https://matrix.example.com",
			UserID:        "egutierrez",
			AccessToken:   "mxat_test",
			StoreDir:      tmpDir,
		}
		_, err := MatrixClientInit(cfg)
		if err == nil {
			t.Fatal("esperaba error con UserID invalido, got nil")
		}
		if !strings.Contains(err.Error(), "UserID") {
			t.Errorf("error deberia mencionar UserID, got: %v", err)
		}
	})

	t.Run("DeviceID vacio Whoami exitoso", func(t *testing.T) {
		const testUserID = "@egutierrez:test.matrix.org"
		const testDeviceID = "ABCDEF1234"

		srv := httptest.NewServer(whoamiHandler(t, http.StatusOK, testUserID, testDeviceID))
		defer srv.Close()

		tmpDir := t.TempDir()
		cfg := MatrixClientInitConfig{
			HomeserverURL: srv.URL,
			UserID:        testUserID,
			AccessToken:   "mxat_valid_token",
			DeviceID:      "", // fuerza Whoami
			StoreDir:      tmpDir,
		}
		res, err := MatrixClientInit(cfg)
		if err != nil {
			t.Fatalf("esperaba nil error, got: %v", err)
		}
		if res.Client == nil {
			t.Fatal("Client es nil")
		}
		if string(res.Client.DeviceID) != testDeviceID {
			t.Errorf("DeviceID: got %q, want %q", res.Client.DeviceID, testDeviceID)
		}
		if string(res.Client.UserID) != testUserID {
			t.Errorf("UserID: got %q, want %q", res.Client.UserID, testUserID)
		}
		if res.Client.AccessToken != "mxat_valid_token" {
			t.Errorf("AccessToken: got %q, want %q", res.Client.AccessToken, "mxat_valid_token")
		}
		if res.StorePath == "" {
			t.Error("StorePath no puede estar vacio")
		}
	})

	t.Run("Whoami 401 token invalido", func(t *testing.T) {
		srv := httptest.NewServer(whoamiHandler(t, http.StatusUnauthorized, "", ""))
		defer srv.Close()

		tmpDir := t.TempDir()
		cfg := MatrixClientInitConfig{
			HomeserverURL: srv.URL,
			UserID:        "@egutierrez:test.matrix.org",
			AccessToken:   "mxat_expired",
			DeviceID:      "", // fuerza Whoami
			StoreDir:      tmpDir,
		}
		_, err := MatrixClientInit(cfg)
		if err == nil {
			t.Fatal("esperaba error con token invalido, got nil")
		}
		// Debe mencionar token invalido o M_UNKNOWN_TOKEN
		errStr := err.Error()
		if !strings.Contains(errStr, "UNKNOWN_TOKEN") && !strings.Contains(errStr, "token") && !strings.Contains(errStr, "Whoami") {
			t.Errorf("error deberia mencionar token/Whoami, got: %v", err)
		}
	})

	t.Run("EnableCrypto true devuelve error not implemented", func(t *testing.T) {
		tmpDir := t.TempDir()
		cfg := MatrixClientInitConfig{
			HomeserverURL: "https://matrix.example.com",
			UserID:        "@user:matrix.example.com",
			AccessToken:   "mxat_test",
			StoreDir:      tmpDir,
			EnableCrypto:  true,
		}
		_, err := MatrixClientInit(cfg)
		if err == nil {
			t.Fatal("esperaba error con EnableCrypto=true, got nil")
		}
		if !strings.Contains(err.Error(), "not implemented") {
			t.Errorf("error deberia mencionar 'not implemented', got: %v", err)
		}
		if !strings.Contains(err.Error(), "0150") {
			t.Errorf("error deberia mencionar issue 0150, got: %v", err)
		}
	})

	t.Run("StoreDir se crea con permisos 0700", func(t *testing.T) {
		if os.Getenv("GOOS") == "windows" {
			t.Skip("permisos Unix no aplican en Windows")
		}

		const testUserID = "@egutierrez:test.matrix.org"
		const testDeviceID = "TESTDEVICE01"

		srv := httptest.NewServer(whoamiHandler(t, http.StatusOK, testUserID, testDeviceID))
		defer srv.Close()

		base := t.TempDir()
		// StoreDir que no existe aun — debe crearse
		storeDir := filepath.Join(base, "matrix_state", "egutierrez")

		cfg := MatrixClientInitConfig{
			HomeserverURL: srv.URL,
			UserID:        testUserID,
			AccessToken:   "mxat_valid",
			DeviceID:      testDeviceID,
			StoreDir:      storeDir,
		}
		res, err := MatrixClientInit(cfg)
		if err != nil {
			t.Fatalf("esperaba nil error, got: %v", err)
		}
		if res.StorePath != storeDir {
			t.Errorf("StorePath: got %q, want %q", res.StorePath, storeDir)
		}

		info, err := os.Stat(storeDir)
		if err != nil {
			t.Fatalf("StoreDir no fue creado: %v", err)
		}
		if !info.IsDir() {
			t.Error("StoreDir no es un directorio")
		}
		// Verificar permisos 0700 (solo propietario)
		mode := info.Mode().Perm()
		if mode != 0700 {
			t.Errorf("permisos StoreDir: got %04o, want 0700", mode)
		}
	})
}
