package ike

import (
	"bytes"
	"context"
	"errors"
	"strings"
	"testing"

	"vocat/internal/vowifi"
)

type testAKAProvider struct {
	result    vowifi.AKAResult
	err       error
	challenge vowifi.AKAChallenge
	calls     int
}

func (provider *testAKAProvider) CheckReady(context.Context, vowifi.SIMIdentity) (vowifi.AKAEvidence, error) {
	return vowifi.AKAEvidence{Ready: true, Application: "USIM"}, nil
}

func (provider *testAKAProvider) Authenticate(
	_ context.Context,
	_ vowifi.SIMIdentity,
	challenge vowifi.AKAChallenge,
) (vowifi.AKAResult, error) {
	provider.calls++
	provider.challenge = challenge
	return provider.result, provider.err
}

func testSIMIdentity() vowifi.SIMIdentity {
	return vowifi.SIMIdentity{
		IMSI:    "234150123456789",
		HomeMCC: "234",
		HomeMNC: "15",
	}
}

func TestEAPAKAIdentityRequiresExactlyOneRequestAttribute(t *testing.T) {
	client, err := newAKAClient(testSIMIdentity(), &testAKAProvider{})
	if err != nil {
		t.Fatalf("newAKAClient() error = %v", err)
	}
	request, err := marshalEAPPacket(eapPacket{
		Code:       eapRequest,
		Identifier: 9,
		Type:       eapTypeAKA,
		Data:       []byte{akaSubtypeIdentity, 0, 0},
	})
	if err != nil {
		t.Fatal(err)
	}
	action, err := client.handle(context.Background(), request)
	if err != nil {
		t.Fatalf("handle malformed identity error = %v", err)
	}
	response, err := parseEAPPacket(action.Response)
	if err != nil {
		t.Fatal(err)
	}
	if response.Code != eapResponse || response.Identifier != 9 ||
		response.Type != eapTypeAKA || len(response.Data) < 1 ||
		response.Data[0] != akaSubtypeClientError {
		t.Fatalf("malformed identity response = %#v", response)
	}

	permanent, _ := marshalAKAAttribute(akaAttrPermanentIDReq, []byte{0, 0})
	validRequest, _ := marshalEAPPacket(eapPacket{
		Code:       eapRequest,
		Identifier: 10,
		Type:       eapTypeAKA,
		Data:       append([]byte{akaSubtypeIdentity, 0, 0}, permanent...),
	})
	action, err = client.handle(context.Background(), validRequest)
	if err != nil {
		t.Fatalf("handle valid identity error = %v", err)
	}
	response, _ = parseEAPPacket(action.Response)
	if response.Data[0] != akaSubtypeIdentity {
		t.Fatalf("valid identity response subtype = %d", response.Data[0])
	}
	attributes, err := parseAKAAttributes(response.Data[3:])
	if err != nil {
		t.Fatal(err)
	}
	identity, err := oneAKAAttribute(attributes, akaAttrIdentity)
	if err != nil {
		t.Fatal(err)
	}
	length := int(identity.Raw[2])<<8 | int(identity.Raw[3])
	if got := string(identity.Raw[4 : 4+length]); got != "0234150123456789@nai.epc.mnc015.mcc234.3gppnetwork.org" {
		t.Fatalf("permanent AKA identity = %q", got)
	}
}

func TestEAPFailureReportsAuthenticationStage(t *testing.T) {
	client, err := newAKAClient(testSIMIdentity(), &testAKAProvider{})
	if err != nil {
		t.Fatal(err)
	}
	failure, err := marshalEAPPacket(eapPacket{Code: eapFailure, Identifier: 3})
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.handle(context.Background(), failure)
	if !errors.Is(err, vowifi.ErrEAPAuthenticationRejected) || !strings.Contains(err.Error(), "before the SIM AKA challenge") {
		t.Fatalf("pre-challenge failure = %v", err)
	}
	client.challengeComplete = true
	_, err = client.handle(context.Background(), failure)
	if !errors.Is(err, vowifi.ErrEAPAuthenticationRejected) || !strings.Contains(err.Error(), "after the SIM AKA response") {
		t.Fatalf("post-challenge failure = %v", err)
	}
}

func TestEAPAKAChallengeTypedSIMAndMAC(t *testing.T) {
	result := vowifi.AKAResult{
		RES: bytes.Repeat([]byte{0x91}, 8),
		CK:  bytes.Repeat([]byte{0x92}, 16),
		IK:  bytes.Repeat([]byte{0x93}, 16),
	}
	provider := &testAKAProvider{result: result}
	client, err := newAKAClient(testSIMIdentity(), provider)
	if err != nil {
		t.Fatal(err)
	}
	keys, err := deriveAKAKeys(client.identity, result.IK, result.CK)
	if err != nil {
		t.Fatal(err)
	}
	randValue := bytes.Repeat([]byte{0xa1}, 16)
	autnValue := bytes.Repeat([]byte{0xa2}, 16)
	randAttribute, _ := marshalAKAAttribute(akaAttrRAND, append([]byte{0, 0}, randValue...))
	autnAttribute, _ := marshalAKAAttribute(akaAttrAUTN, append([]byte{0, 0}, autnValue...))
	macAttribute, _ := marshalAKAAttribute(akaAttrMAC, make([]byte, 18))
	data := []byte{akaSubtypeChallenge, 0, 0}
	data = append(data, randAttribute...)
	data = append(data, autnAttribute...)
	data = append(data, macAttribute...)
	request, _ := marshalEAPPacket(eapPacket{
		Code:       eapRequest,
		Identifier: 21,
		Type:       eapTypeAKA,
		Data:       data,
	})
	attributes, _ := parseAKAAttributes(data[3:])
	mac, _ := oneAKAAttribute(attributes, akaAttrMAC)
	macOffset := 5 + 3 + mac.Offset
	copy(request[macOffset+4:macOffset+20], akaMAC(keys.KAut, request))

	action, err := client.handle(context.Background(), request)
	if err != nil {
		t.Fatalf("handle challenge error = %v", err)
	}
	if provider.calls != 1 || !bytes.Equal(provider.challenge.RAND[:], randValue) ||
		!bytes.Equal(provider.challenge.AUTN[:], autnValue) {
		t.Fatalf("typed SIM challenge = %#v calls=%d", provider.challenge, provider.calls)
	}
	response, err := parseEAPPacket(action.Response)
	if err != nil {
		t.Fatal(err)
	}
	if response.Data[0] != akaSubtypeChallenge {
		t.Fatalf("challenge response subtype = %d", response.Data[0])
	}
	responseAttributes, err := parseAKAAttributes(response.Data[3:])
	if err != nil {
		t.Fatal(err)
	}
	res, err := oneAKAAttribute(responseAttributes, akaAttrRES)
	if err != nil {
		t.Fatal(err)
	}
	if bits := int(res.Raw[2])<<8 | int(res.Raw[3]); bits != len(result.RES)*8 {
		t.Fatalf("AT_RES bits = %d", bits)
	}
	responseMAC, err := oneAKAAttribute(responseAttributes, akaAttrMAC)
	if err != nil {
		t.Fatal(err)
	}
	zeroed := append([]byte(nil), action.Response...)
	responseOffset := 5 + 3 + responseMAC.Offset
	actualMAC := append([]byte(nil), zeroed[responseOffset+4:responseOffset+20]...)
	for index := responseOffset + 4; index < responseOffset+20; index++ {
		zeroed[index] = 0
	}
	if !bytes.Equal(actualMAC, akaMAC(keys.KAut, zeroed)) {
		t.Fatal("response AT_MAC does not authenticate the complete EAP packet")
	}
	success, _ := marshalEAPPacket(eapPacket{Code: eapSuccess, Identifier: 22})
	finalAction, err := client.handle(context.Background(), success)
	if err != nil || !finalAction.Success {
		t.Fatalf("authenticated EAP success = %#v err=%v", finalAction, err)
	}
}

func TestEAPAKAUSIMNetworkAuthenticationFailureSendsReject(t *testing.T) {
	provider := &testAKAProvider{err: vowifi.ErrEC20AKAMACFailure}
	client, err := newAKAClient(testSIMIdentity(), provider)
	if err != nil {
		t.Fatal(err)
	}
	randAttribute, _ := marshalAKAAttribute(akaAttrRAND, append([]byte{0, 0}, bytes.Repeat([]byte{1}, 16)...))
	autnAttribute, _ := marshalAKAAttribute(akaAttrAUTN, append([]byte{0, 0}, bytes.Repeat([]byte{2}, 16)...))
	macAttribute, _ := marshalAKAAttribute(akaAttrMAC, make([]byte, 18))
	data := append([]byte{akaSubtypeChallenge, 0, 0}, randAttribute...)
	data = append(data, autnAttribute...)
	data = append(data, macAttribute...)
	request, _ := marshalEAPPacket(eapPacket{Code: eapRequest, Identifier: 5, Type: eapTypeAKA, Data: data})
	action, err := client.handle(context.Background(), request)
	if err != nil {
		t.Fatalf("handle USIM MAC failure error = %v", err)
	}
	response, _ := parseEAPPacket(action.Response)
	if len(response.Data) != 3 || response.Data[0] != akaSubtypeAuthReject {
		t.Fatalf("USIM MAC failure response = %#v", response)
	}
	if !errors.Is(provider.err, vowifi.ErrEC20AKAMACFailure) {
		t.Fatal("test provider lost the MAC failure sentinel")
	}
}
