package main

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log/slog"
	"testing"
	"time"

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

// fakeModemClient is a minimal scripted modem.Client for exercising the region
// enforcement orchestration (flight-mode flips) without hardware.
type fakeModemClient struct {
	steps []fakeStep
	index int
}

type fakeStep struct {
	command string
	lines   []string
}

func (client *fakeModemClient) Execute(_ context.Context, command string) (modem.Response, error) {
	if client.index >= len(client.steps) {
		return modem.Response{}, fmt.Errorf("unexpected command %q", command)
	}
	step := client.steps[client.index]
	client.index++
	if command != step.command {
		return modem.Response{}, fmt.Errorf("command %q, want %q", command, step.command)
	}
	return modem.Response{Command: command, Lines: step.lines, Final: "OK"}, nil
}

func (client *fakeModemClient) WaitURC(context.Context, func(string) bool) (string, error) {
	return "", errors.New("no URC scripted")
}

func (client *fakeModemClient) Close() error { return nil }

func (client *fakeModemClient) assertExhausted(t *testing.T) {
	t.Helper()
	if client.index != len(client.steps) {
		t.Fatalf("consumed %d of %d scripted commands", client.index, len(client.steps))
	}
}

type fakeDiscoverer struct{ candidates []modem.Candidate }

func (discoverer fakeDiscoverer) Discover(context.Context) ([]modem.Candidate, error) {
	return discoverer.candidates, nil
}

type fakeOpener struct{ client modem.Client }

func (opener fakeOpener) Open(context.Context, modem.Port) (modem.Client, error) {
	return opener.client, nil
}

const regionTestDeviceID = "quectel-region-test"

func newRegionTestManager(t *testing.T, client modem.Client) *device.Manager {
	t.Helper()
	manager, err := device.NewManager(device.Options{
		Discoverer: fakeDiscoverer{candidates: []modem.Candidate{{
			ID:      regionTestDeviceID,
			Product: "EC20",
			ATPort:  modem.Port{Path: "/dev/ttyUSB2", Role: modem.PortRoleAT},
		}}},
		Opener:         fakeOpener{client: client},
		CommandTimeout: time.Second,
		LongTimeout:    time.Second,
	})
	if err != nil {
		t.Fatalf("NewManager: %v", err)
	}
	if err := manager.Start(context.Background()); err != nil {
		t.Fatalf("Start: %v", err)
	}
	t.Cleanup(func() { _ = manager.Stop(context.Background()) })
	return manager
}

func newRegionTestStore(t *testing.T) *store.Store {
	t.Helper()
	database, err := store.Open(context.Background(), ":memory:")
	if err != nil {
		t.Fatalf("store.Open: %v", err)
	}
	t.Cleanup(func() { _ = database.Close() })
	return database
}

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

func TestEnforceCardRegionForcesAirplaneAndPersistsPolicy(t *testing.T) {
	client := &fakeModemClient{steps: []fakeStep{
		{command: "AT+CFUN?", lines: []string{"+CFUN: 1"}},
		{command: "AT+CFUN=4"},
		{command: "AT+CFUN?", lines: []string{"+CFUN: 4"}},
	}}
	manager := newRegionTestManager(t, client)
	database := newRegionTestStore(t)

	snapshot := &device.Snapshot{
		DeviceID: regionTestDeviceID,
		SIMReady: true,
		IMSI:     "460001234567890",
		ICCID:    "89860012345678901234",
	}
	enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
	client.assertExhausted(t)

	policy, err := database.CardPolicy(context.Background(), snapshot.ICCID)
	if err != nil {
		t.Fatalf("CardPolicy: %v", err)
	}
	if policy.Source != cardPolicySourceRegionBlock {
		t.Fatalf("policy source = %q, want %q", policy.Source, cardPolicySourceRegionBlock)
	}
	if policy.NetworkEnabled || policy.VoWiFiEnabled || !policy.AirplaneEnabled {
		t.Fatalf("policy switches = %#v, want all service off and airplane on", policy)
	}
}

func TestEnforceCardRegionSkipsRadioWhenAlreadyOff(t *testing.T) {
	client := &fakeModemClient{}
	manager := newRegionTestManager(t, client)
	database := newRegionTestStore(t)

	snapshot := &device.Snapshot{
		DeviceID:   regionTestDeviceID,
		SIMReady:   true,
		IMSI:       "461001234567890",
		ICCID:      "89860012345678901234",
		FlightMode: true,
	}
	enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
	client.assertExhausted(t)

	if _, err := database.CardPolicy(context.Background(), snapshot.ICCID); err != nil {
		t.Fatalf("expected a persisted block policy even with the radio already off: %v", err)
	}
}

func TestEnforceCardRegionLiftsBlockForAllowedSIM(t *testing.T) {
	client := &fakeModemClient{steps: []fakeStep{
		{command: "AT+CFUN?", lines: []string{"+CFUN: 4"}},
		{command: "AT+CFUN=1"},
		{command: "AT+CFUN?", lines: []string{"+CFUN: 1"}},
	}}
	manager := newRegionTestManager(t, client)
	database := newRegionTestStore(t)

	if err := database.UpsertCardPolicy(context.Background(), store.CardPolicy{
		ICCID:           "89860012345678901234",
		AirplaneEnabled: true,
		IPVersion:       "IPV4V6",
		Source:          cardPolicySourceRegionBlock,
	}); err != nil {
		t.Fatalf("seed block policy: %v", err)
	}

	snapshot := &device.Snapshot{
		DeviceID:   regionTestDeviceID,
		SIMReady:   true,
		IMSI:       "310260123456789",
		ICCID:      "89012601234567890123",
		FlightMode: true,
	}
	enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
	client.assertExhausted(t)

	if _, err := database.CardPolicy(context.Background(), "89860012345678901234"); !errors.Is(err, store.ErrNotFound) {
		t.Fatalf("expected the auto block policy to be cleared, got err=%v", err)
	}
}

func TestEnforceCardRegionLeavesAllowedSIMWithoutPriorBlockAlone(t *testing.T) {
	client := &fakeModemClient{}
	manager := newRegionTestManager(t, client)
	database := newRegionTestStore(t)

	snapshot := &device.Snapshot{
		DeviceID: regionTestDeviceID,
		SIMReady: true,
		IMSI:     "310260123456789",
		ICCID:    "89012601234567890123",
	}
	enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, snapshot)
	client.assertExhausted(t)
}

func TestEnforceCardRegionIgnoresUnknownOrNotReadySIM(t *testing.T) {
	client := &fakeModemClient{}
	manager := newRegionTestManager(t, client)
	database := newRegionTestStore(t)

	// Not ready: no action at all.
	notReady := &device.Snapshot{DeviceID: regionTestDeviceID, SIMReady: false, IMSI: "460001234567890"}
	enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, notReady)

	// Ready but IMSI unknown: hold state, neither block nor lift.
	unknown := &device.Snapshot{DeviceID: regionTestDeviceID, SIMReady: true, IMSI: ""}
	enforceCardRegion(context.Background(), regionTestLogger(), database, manager, regionTestDeviceID, unknown)

	client.assertExhausted(t)
	policies, err := database.ListCardPolicies(context.Background())
	if err != nil {
		t.Fatalf("ListCardPolicies: %v", err)
	}
	if len(policies) != 0 {
		t.Fatalf("expected no card policies, got %d", len(policies))
	}
}
