package crypto

import (
	"github.com/deepch/vdk/codec/h265parser"
	"m7s.live/v5/pkg"
	"m7s.live/v5/pkg/codec"
	"m7s.live/v5/pkg/task"

	"fmt"

	m7s "m7s.live/v5"
	"m7s.live/v5/plugin/crypto/pkg/method"
)

// GlobalConfig 全局加密配置
var GlobalConfig Config

type Config struct {
	IsStatic   bool   `desc:"是否静态密钥" default:"false"`
	Algo       string `desc:"加密算法" default:"aes_ctr"` //加密算法
	EncryptLen int    `desc:"加密字节长度" default:"1024"`  //加密字节长度
	Secret     struct {
		Key string `desc:"加密密钥" default:"your key"` //加密密钥
		Iv  string `desc:"加密向量" default:"your iv"`  //加密向量
	} `desc:"密钥配置"`
}

type Transform struct {
	m7s.DefaultTransformer
	cryptor method.ICryptor
}

func NewTransform() m7s.ITransformer {
	ret := &Transform{}
	ret.SetDescription(task.OwnerTypeKey, "Crypto")
	return ret
}

// ValidateAndCreateKey 验证并创建加密密钥
func ValidateAndCreateKey(isStatic bool, algo string, secretKey, secretIv, streamPath string) (keyConf method.Key, err error) {
	if isStatic {
		switch algo {
		case "aes_ctr":
			keyConf.Key = secretKey
			keyConf.Iv = secretIv
			if len(keyConf.Iv) != 16 || len(keyConf.Key) != 32 {
				return keyConf, fmt.Errorf("key or iv length is wrong")
			}
		case "xor_s":
			keyConf.Key = secretKey
			if len(keyConf.Key) != 32 {
				return keyConf, fmt.Errorf("key length is wrong")
			}
		case "xor_c":
			keyConf.Key = secretKey
			keyConf.Iv = secretIv
			if len(keyConf.Iv) != 16 || len(keyConf.Key) != 32 {
				return keyConf, fmt.Errorf("key or iv length is wrong")
			}
		default:
			return keyConf, fmt.Errorf("algo type is wrong")
		}
	} else {
		/*
			动态加密
			key = md5(密钥+流名称)
			iv = md5(流名称）前一半
		*/
		if secretKey != "" {
			keyConf.Key = method.Md5Sum(secretKey + streamPath)
			keyConf.Iv = method.Md5Sum(streamPath)[:16]
		} else {
			return keyConf, fmt.Errorf("secret key is empty")
		}
	}
	return
}

func (t *Transform) Start() error {
	// 在 Start 时获取并保存配置
	t.Info("transform job started")

	keyConf, err := ValidateAndCreateKey(GlobalConfig.IsStatic, GlobalConfig.Algo, GlobalConfig.Secret.Key, GlobalConfig.Secret.Iv, t.TransformJob.StreamPath)
	if err != nil {
		return err
	}

	t.cryptor, err = method.GetCryptor(GlobalConfig.Algo, keyConf)
	if err != nil {
		t.Error("failed to create cryptor", "error", err)
		return err
	}

	// 使用 TransformJob 的 Subscribe 方法订阅流
	if err := t.TransformJob.Subscribe(); err != nil {
		t.Error("failed to subscribe stream", "error", err)
		return err
	}

	t.Info("crypto transform started",
		"stream", t.TransformJob.StreamPath,
		"algo", GlobalConfig.Algo,
		"isStatic", GlobalConfig.IsStatic,
	)

	return nil
}

func (t *Transform) Go() error {
	// 创建发布者
	if err := t.TransformJob.Publish(t.TransformJob.StreamPath + "/crypto"); err != nil {
		t.Error("failed to create publisher", "error", err)
		return err
	}

	// 处理音视频流
	return m7s.PlayBlock(t.TransformJob.Subscriber,
		func(audio *pkg.RawAudio) (err error) {
			copyAudio := &pkg.RawAudio{
				FourCC:    audio.FourCC,
				Timestamp: audio.Timestamp,
			}
			audio.Memory.Range(func(b []byte) {
				copy(copyAudio.NextN(len(b)), b)
			})
			return t.TransformJob.Publisher.WriteAudio(copyAudio)
		},
		func(video *pkg.H26xFrame) error {
			// 处理视频帧
			if video.GetSize() == 0 {
				return nil
			}
			copyVideo := &pkg.H26xFrame{
				FourCC:    video.FourCC,
				CTS:       video.CTS,
				Timestamp: video.Timestamp,
			}

			for _, nalu := range video.Nalus {
				mem := copyVideo.NextN(nalu.Size)
				copy(mem, nalu.ToBytes())
				needEncrypt := false
				if video.FourCC == codec.FourCC_H264 {
					switch codec.ParseH264NALUType(mem[0]) {
					case codec.NALU_Non_IDR_Picture, codec.NALU_IDR_Picture:
						needEncrypt = true
					}
				} else if video.FourCC == codec.FourCC_H265 {
					switch codec.ParseH265NALUType(mem[0]) {
					case h265parser.NAL_UNIT_CODED_SLICE_BLA_W_LP,
						h265parser.NAL_UNIT_CODED_SLICE_BLA_W_RADL,
						h265parser.NAL_UNIT_CODED_SLICE_BLA_N_LP,
						h265parser.NAL_UNIT_CODED_SLICE_IDR_W_RADL,
						h265parser.NAL_UNIT_CODED_SLICE_IDR_N_LP,
						h265parser.NAL_UNIT_CODED_SLICE_CRA:
						needEncrypt = true
					}
				}
				if needEncrypt {
					encBytes, err := t.cryptor.Encrypt(mem[2:])
					if err == nil {
						copyVideo.Nalus.Append(append([]byte{mem[0], mem[1]}, encBytes...))
					} else {
						copyVideo.Nalus.Append(mem)
					}
				} else {
					copyVideo.Nalus.Append(mem)
				}
			}
			return t.TransformJob.Publisher.WriteVideo(copyVideo)
		})
}

func (t *Transform) Dispose() {
	t.Info("crypto transform disposed",
		"stream", t.TransformJob.StreamPath,
	)
}
