// Package integration connects the protocol runtime to vocat's persistent
// configuration and modem inventory. It contains no IKE, IMS, or SIM protocol
// implementation.
package integration

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"vocat/internal/device"
	"vocat/internal/store"
	"vocat/internal/vowifi"
)

type ProxyResolver struct {
	Store *store.Store
}

func (resolver ProxyResolver) Resolve(
	ctx context.Context,
	request vowifi.ProxyRequest,
) (vowifi.ProxyRoute, error) {
	if resolver.Store == nil {
		return vowifi.ProxyRoute{}, errors.New("vowifi proxy resolver: store is nil")
	}
	deviceID := strings.TrimSpace(request.DeviceID)
	if deviceID == "" {
		return vowifi.ProxyRoute{Mode: vowifi.ProxyModeDirect}, nil
	}
	binding, err := resolver.Store.DeviceProxyBinding(ctx, deviceID)
	if errors.Is(err, store.ErrNotFound) {
		return vowifi.ProxyRoute{Mode: vowifi.ProxyModeDirect}, nil
	}
	if err != nil {
		return vowifi.ProxyRoute{}, fmt.Errorf("resolve proxy binding for device %s: %w", deviceID, err)
	}
	upstream, err := resolver.Store.UpstreamProxy(ctx, binding.UpstreamProxyID)
	if err != nil {
		return vowifi.ProxyRoute{}, fmt.Errorf(
			"load upstream proxy %q for device %s: %w",
			binding.UpstreamProxyID,
			deviceID,
			err,
		)
	}
	if !upstream.Enabled {
		return vowifi.ProxyRoute{}, fmt.Errorf(
			"upstream proxy %q for device %s is disabled",
			upstream.ID,
			deviceID,
		)
	}
	return vowifi.ProxyRoute{
		Mode:     vowifi.ProxyModeSOCKS5,
		ID:       upstream.ID,
		Address:  upstream.Addr,
		Username: upstream.Username,
		Password: upstream.Password,
	}, nil
}

type PhoneStore struct {
	Store    *store.Store
	DeviceID string
}

func (phones PhoneStore) SaveAssociatedNumber(
	ctx context.Context,
	record vowifi.PhoneRecord,
) error {
	if phones.Store == nil {
		return errors.New("vowifi phone store: store is nil")
	}
	switch record.Source {
	case vowifi.PhoneSourceAssociatedMSISDN, vowifi.PhoneSourcePAssociatedURI:
	default:
		return fmt.Errorf("vowifi phone store: untrusted source %q", record.Source)
	}
	return phones.Store.UpsertPhoneAssociation(ctx, store.PhoneAssociation{
		ICCID:     record.ICCID,
		DeviceID:  strings.TrimSpace(phones.DeviceID),
		Number:    record.Number,
		Source:    record.Source,
		UpdatedAt: record.UpdatedAt,
	})
}

type DeviceReader interface {
	Get(string) (device.Device, error)
}

type StateProjector struct {
	Store   *store.Store
	Devices DeviceReader
}

func (projector StateProjector) Save(
	ctx context.Context,
	state vowifi.State,
) error {
	if projector.Store == nil {
		return errors.New("vowifi state projector: store is nil")
	}
	runtime := store.VoWiFiRuntime{
		DeviceID:          state.DeviceID,
		Phase:             string(state.Phase),
		DataplaneMode:     dataplaneMode(state),
		SIMReady:          state.SIMReady,
		AccessReady:       state.AccessReady,
		TunnelReady:       state.TunnelReady,
		IMSReady:          state.IMSReady,
		SMSReady:          state.SMSReady,
		RegStatus:         boolInt(state.IMSReady),
		RegStatusText:     registrationText(state),
		NetworkMode:       "Wi-Fi",
		LocalPhone:        state.PhoneNumber,
		PhoneNumberSource: state.PhoneNumberSource,
		LastErrorClass:    state.LastErrorClass,
		LastError:         state.LastError,
		LastReason:        state.LastReason,
		UpdatedAt:         state.UpdatedAt,
	}
	if projector.Devices != nil {
		if entry, err := projector.Devices.Get(state.DeviceID); err == nil && entry.Snapshot != nil {
			runtime.ICCID = strings.TrimSpace(entry.Snapshot.ICCID)
			runtime.IMSI = strings.TrimSpace(entry.Snapshot.IMSI)
		}
	}
	if runtime.LocalPhone == "" && runtime.ICCID != "" {
		if association, err := projector.Store.PhoneAssociation(ctx, runtime.ICCID); err == nil {
			runtime.LocalPhone = association.Number
			runtime.PhoneNumberSource = association.Source
		} else if !errors.Is(err, store.ErrNotFound) {
			return fmt.Errorf("restore associated phone number: %w", err)
		}
	}

	var err error
	runtime.Tunnel, err = marshalObject(map[string]any{
		"established":    state.TunnelReady,
		"name":           state.TunnelName,
		"dataplane_mode": state.DataplaneMode,
		"epdg":           state.EPDG,
		"proxy_mode":     state.ProxyMode,
		"proxy_id":       state.ProxyID,
		"security_audit": state.Security,
	})
	if err != nil {
		return err
	}
	runtime.IMSCore, err = marshalObject(map[string]any{
		"registered":         state.IMSReady,
		"registration_state": state.IMSRegistration,
		"associated_number":  runtime.LocalPhone,
		"number_source":      runtime.PhoneNumberSource,
	})
	if err != nil {
		return err
	}
	runtime.SMSIP, err = marshalObject(map[string]any{
		"ready": state.SMSReady,
	})
	if err != nil {
		return err
	}
	runtime.Extra, err = marshalObject(map[string]any{
		"enabled":              state.Enabled,
		"active":               state.Active,
		"pure_airplane_policy": state.PureAirplanePolicy,
		"home_mcc":             state.HomeMCC,
		"home_mnc":             state.HomeMNC,
		"warnings":             state.Warnings,
		"cleanup_errors":       state.CleanupErrors,
		"attempt":              state.Attempt,
		"sequence":             state.Sequence,
		"started_at":           state.StartedAt,
	})
	if err != nil {
		return err
	}
	return projector.Store.UpsertVoWiFiRuntime(ctx, runtime)
}

func dataplaneMode(state vowifi.State) string {
	if value := strings.TrimSpace(state.DataplaneMode); value == "userspace" || value == "xfrm" {
		return value
	}
	if state.TunnelReady || state.Phase == vowifi.PhaseTunnelReady ||
		state.Phase == vowifi.PhaseIMSReady || state.Phase == vowifi.PhaseSMSReady {
		return "ipsec"
	}
	return ""
}

func registrationText(state vowifi.State) string {
	if value := strings.TrimSpace(state.IMSRegistration); value != "" {
		return value
	}
	if state.IMSReady {
		return "registered"
	}
	if state.Phase == vowifi.PhaseFailed &&
		strings.HasPrefix(state.LastErrorClass, "ims") {
		return "registration failed"
	}
	return "not registered"
}

func boolInt(value bool) int {
	if value {
		return 1
	}
	return 0
}

func marshalObject(value map[string]any) (json.RawMessage, error) {
	encoded, err := json.Marshal(value)
	if err != nil {
		return nil, fmt.Errorf("marshal VoWiFi runtime projection: %w", err)
	}
	return encoded, nil
}
