package server

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"vocat/internal/device"
	"vocat/internal/modem"
	"vocat/internal/store"
)

// fakeDeviceController is a stub DeviceController that resolves one discovered
// device carrying a fixed snapshot, enough to drive the region guards. The scan,
// USSD, and USB-net results are configurable for the feature endpoint tests.
type fakeDeviceController struct {
	entry      device.Device
	scanResult device.OperatorScanResult
	scanErr    error
	ussdResult device.USSDResult
	ussdErr    error
	usbNetMode device.USBNetMode
	usbNetErr  error
}

func (f fakeDeviceController) Discover(context.Context) ([]device.Device, error) {
	return []device.Device{f.entry}, nil
}
func (f fakeDeviceController) List() []device.Device { return []device.Device{f.entry} }
func (f fakeDeviceController) Get(id string) (device.Device, error) {
	if id == f.entry.ID {
		return f.entry, nil
	}
	return device.Device{}, device.ErrNotFound
}
func (f fakeDeviceController) Refresh(context.Context, string) (device.Snapshot, error) {
	return device.Snapshot{}, nil
}
func (f fakeDeviceController) ExecuteAT(context.Context, string, string) (modem.Response, error) {
	return modem.Response{}, nil
}
func (f fakeDeviceController) Reboot(context.Context, string) error { return nil }
func (f fakeDeviceController) USSD(context.Context, string, string) (device.USSDResult, error) {
	return device.USSDResult{}, nil
}
func (f fakeDeviceController) ContinueUSSD(context.Context, string, string) (device.USSDResult, error) {
	return f.ussdResult, f.ussdErr
}
func (f fakeDeviceController) CancelUSSD(context.Context, string) error { return f.ussdErr }
func (f fakeDeviceController) SetFlight(context.Context, string, bool) (device.FlightResult, error) {
	return device.FlightResult{}, nil
}
func (f fakeDeviceController) SetNetwork(context.Context, string, device.NetworkRequest) (device.NetworkResult, error) {
	return device.NetworkResult{}, nil
}
func (f fakeDeviceController) USBNetMode(context.Context, string) (device.USBNetMode, error) {
	return device.USBNetMode{}, nil
}
func (f fakeDeviceController) SetUSBNetMode(context.Context, string, int) (device.USBNetMode, error) {
	return device.USBNetMode{}, nil
}
func (f fakeDeviceController) SetUSBNetModeByPort(context.Context, string, int) (device.USBNetMode, error) {
	return f.usbNetMode, f.usbNetErr
}
func (f fakeDeviceController) OperatorSelection(context.Context, string) (device.OperatorSelection, error) {
	return device.OperatorSelection{}, nil
}
func (f fakeDeviceController) SetOperatorSelection(context.Context, string, bool, string, *int) (device.OperatorSelection, error) {
	return device.OperatorSelection{}, nil
}
func (f fakeDeviceController) ScanOperators(context.Context, string) (device.OperatorScanResult, error) {
	return f.scanResult, f.scanErr
}
func (f fakeDeviceController) SendSMS(context.Context, string, string, string) (device.SMSSendResult, error) {
	return device.SMSSendResult{}, nil
}
func (f fakeDeviceController) ListSMS(context.Context, string) ([]device.SMSMessage, error) {
	return nil, nil
}
func (f fakeDeviceController) ReadSMS(context.Context, string, int) (device.SMSMessage, error) {
	return device.SMSMessage{}, nil
}
func (f fakeDeviceController) DeleteSMS(context.Context, string, int) error { return nil }
func (f fakeDeviceController) ESIMListProfiles(context.Context, string) (device.EsimInfo, error) {
	return device.EsimInfo{}, nil
}
func (f fakeDeviceController) ESIMInventory(context.Context, string) ([]device.EsimInventoryEntry, error) {
	return []device.EsimInventoryEntry{}, nil
}
func (f fakeDeviceController) ESIMSwitchProfile(context.Context, string, string, string) error {
	return nil
}
func (f fakeDeviceController) ESIMDisableProfile(context.Context, string, string, string) error {
	return nil
}
func (f fakeDeviceController) ESIMRenameProfile(context.Context, string, string, string, string) error {
	return nil
}
func (f fakeDeviceController) ESIMDownloadProfile(context.Context, string, device.EsimDownloadParams, func(device.EsimProgress)) (*device.EsimDownloadResult, error) {
	return nil, errors.New("not implemented in test fake")
}
func (f fakeDeviceController) ESIMDeleteProfile(context.Context, string, string, string) (*device.EsimDeleteResult, error) {
	return &device.EsimDeleteResult{}, nil
}
func (f fakeDeviceController) ESIMChipInfo(context.Context, string) (*device.EsimChipInfo, error) {
	return nil, errors.New("not implemented in test fake")
}

func regionTestLogger() *slog.Logger {
	return slog.New(slog.NewTextHandler(io.Discard, nil))
}

func blockedRegionServer(t *testing.T, imsi string) *Server {
	t.Helper()
	database, err := store.Open(context.Background(), ":memory:")
	if err != nil {
		t.Fatalf("store.Open: %v", err)
	}
	t.Cleanup(func() { _ = database.Close() })
	return &Server{
		store:               database,
		logger:              regionTestLogger(),
		maxRequestBodyBytes: 4096,
		devices: fakeDeviceController{entry: device.Device{
			ID:         "dev1",
			Discovered: true,
			Snapshot:   &device.Snapshot{DeviceID: "dev1", IMSI: imsi},
		}},
		vowifi: &fakeVoWiFiController{},
	}
}

func TestModemSummaryRegionFields(t *testing.T) {
	t.Parallel()
	blocked := modemSummary(&device.Snapshot{IMSI: "460001234567890"}, "", "")
	if blocked["service_blocked"] != true {
		t.Fatalf("service_blocked = %v, want true", blocked["service_blocked"])
	}
	if blocked["card_mcc"] != "460" || blocked["card_country"] != "中国" {
		t.Fatalf("card_mcc=%v card_country=%v", blocked["card_mcc"], blocked["card_country"])
	}
	if reason, _ := blocked["blocked_reason"].(string); reason == "" {
		t.Fatal("blocked_reason must be set for a blocked card")
	}

	allowed := modemSummary(&device.Snapshot{IMSI: "310260123456789"}, "", "")
	if allowed["service_blocked"] != false || allowed["blocked_reason"] != "" {
		t.Fatalf("allowed card summary = %v / %v", allowed["service_blocked"], allowed["blocked_reason"])
	}
	if allowed["card_mcc"] != "310" || allowed["card_country"] != "美国" {
		t.Fatalf("allowed card_mcc=%v card_country=%v", allowed["card_mcc"], allowed["card_country"])
	}

	empty := modemSummary(nil, "", "")
	if empty["service_blocked"] != false || empty["card_mcc"] != "" {
		t.Fatalf("nil snapshot summary = %v / %v", empty["service_blocked"], empty["card_mcc"])
	}
}

func TestWriteDeviceErrorMapsRegionBlockedTo403(t *testing.T) {
	t.Parallel()
	server := &Server{logger: regionTestLogger()}
	recorder := httptest.NewRecorder()
	server.writeDeviceError(recorder, device.ErrRegionBlocked)
	if recorder.Code != http.StatusForbidden {
		t.Fatalf("status = %d, want 403", recorder.Code)
	}
	var envelope errorEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
		t.Fatal(err)
	}
	if envelope.Error.Code != "region_blocked" {
		t.Fatalf("error code = %q, want region_blocked", envelope.Error.Code)
	}
}

func TestCountryNameForMCC(t *testing.T) {
	t.Parallel()
	cases := map[string]string{
		"460": "中国",
		"461": "中国",
		"454": "中国香港",
		"466": "中国台湾",
		"310": "美国",
		"":    "",
		"999": "",
	}
	for mcc, want := range cases {
		if got := countryNameForMCC(mcc); got != want {
			t.Errorf("countryNameForMCC(%q) = %q, want %q", mcc, got, want)
		}
	}
}

func TestHandleVoWiFiEnabledBlockedRegion(t *testing.T) {
	server := blockedRegionServer(t, "460001234567890")
	request := httptest.NewRequest(http.MethodPatch, "/devices/dev1/vowifi", strings.NewReader(`{"enabled":true}`))
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()

	config := store.Device{ID: "dev1"}
	if handled := server.handleVoWiFiEnabled(recorder, request, config, true); !handled {
		t.Fatal("handler did not claim the request")
	}
	if recorder.Code != http.StatusForbidden {
		t.Fatalf("status = %d, want 403", recorder.Code)
	}
	var envelope errorEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
		t.Fatal(err)
	}
	if envelope.Error.Code != "region_blocked" {
		t.Fatalf("error code = %q, want region_blocked", envelope.Error.Code)
	}
	// The block must happen before any state change is persisted.
	if _, err := server.store.Device(context.Background(), "dev1"); !errors.Is(err, store.ErrNotFound) {
		t.Fatalf("device config must not be written for a blocked region, err=%v", err)
	}
}

func TestHandleVoWiFiEnabledAllowedRegionPassesGuard(t *testing.T) {
	server := blockedRegionServer(t, "310260123456789")
	// Seed the device so the post-guard UpsertDevice succeeds.
	if err := server.store.UpsertDevice(context.Background(), store.Device{ID: "dev1", Name: "EC20"}); err != nil {
		t.Fatalf("seed device: %v", err)
	}
	request := httptest.NewRequest(http.MethodPatch, "/devices/dev1/vowifi", strings.NewReader(`{"enabled":true}`))
	request.Header.Set("Content-Type", "application/json")
	recorder := httptest.NewRecorder()

	server.handleVoWiFiEnabled(recorder, request, store.Device{ID: "dev1"}, true)
	if recorder.Code == http.StatusForbidden {
		t.Fatalf("allowed region must not be blocked, got 403: %s", recorder.Body.String())
	}
}
