// pack.go — упаковщик/распаковщик ZIP-пакетов для ИШ НРД.
//
// Формат отправляемого пакета (раздел 2.3 инструкции):
//   ZIP-архив содержит:
//     - <doc>.xml — сам документ (M2MTransferRequest.xml)
//     - config.xml — настроечный файл с указанием name и package
//
// Пример config.xml:
//   <config>
//     <name>doc.xml</name>
//     <package>#M2MTR</package>
//   </config>
//
// Формат входящего пакета (раздел 2.4 + раздел 2.6):
//   ZIP-архив содержит:
//     - <doc>.xml — сам документ (M2MTransferDecision.xml или M2MTransferResponse.xml)
//     - winf.xml — транзитный конверт ЭДО НРД
//     - <doc>.xml.sgn — отсоединённая подпись НРД (опц.)
//
// ИШ сам формирует пакет ЭДО (подписывает, добавляет winf.xml и т.д.).
// Наша задача — собрать ZIP с XML+config.xml и отправить.

package igw

import (
	"archive/zip"
	"bytes"
	"encoding/xml"
	"errors"
	"fmt"
	"io"
	"path/filepath"
	"strings"

	"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
	"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml"
)

// config — содержимое config.xml внутри пакета.
type config struct {
	XMLName xml.Name `xml:"config"`
	Name    string   `xml:"name"`
	Package string   `xml:"package"`
}

// PackRequest упаковывает M2MTransferRequest в ZIP-архив для ИШ.
// docFileName — имя XML внутри архива (например "M2MTransferRequest.xml").
// Возвращает байты ZIP, готовые к отправке через POST /api/package/{channel}/file.
func PackRequest(req *m2m.M2MTransferRequest, docFileName string) ([]byte, error) {
	if req == nil {
		return nil, errors.New("igw: PackRequest: req=nil")
	}
	if docFileName == "" {
		docFileName = "M2MTransferRequest.xml"
	}
	xmlBytes, err := nsdxml.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("igw: marshal M2MTransferRequest: %w", err)
	}
	return packZIP(xmlBytes, docFileName, "#M2MTR")
}

// PackXML упаковывает произвольный XML в ZIP с config.xml. Тип пакета
// (например "#M2MTR" / "#M2MTD") задаётся явно. Полезно когда XML уже
// собран снаружи (тесты, эталонные сообщения из инструкции).
func PackXML(xmlBytes []byte, docFileName, packageType string) ([]byte, error) {
	if len(xmlBytes) == 0 {
		return nil, errors.New("igw: PackXML: xmlBytes пустой")
	}
	if !strings.HasPrefix(packageType, "#") {
		return nil, fmt.Errorf("igw: packageType должен начинаться с #, получено %q", packageType)
	}
	if docFileName == "" {
		docFileName = "doc.xml"
	}
	return packZIP(xmlBytes, docFileName, packageType)
}

func packZIP(xmlBytes []byte, docFileName, packageType string) ([]byte, error) {
	var buf bytes.Buffer
	w := zip.NewWriter(&buf)

	// 1. сам документ
	fw, err := w.Create(docFileName)
	if err != nil {
		return nil, fmt.Errorf("igw: create %s in zip: %w", docFileName, err)
	}
	if _, err := fw.Write(xmlBytes); err != nil {
		return nil, err
	}

	// 2. config.xml
	cfg := config{Name: docFileName, Package: packageType}
	cfgBytes, err := xml.MarshalIndent(cfg, "", "  ")
	if err != nil {
		return nil, fmt.Errorf("igw: marshal config.xml: %w", err)
	}
	cfgWriter, err := w.Create("config.xml")
	if err != nil {
		return nil, fmt.Errorf("igw: create config.xml in zip: %w", err)
	}
	if _, err := cfgWriter.Write(cfgBytes); err != nil {
		return nil, err
	}

	if err := w.Close(); err != nil {
		return nil, fmt.Errorf("igw: close zip: %w", err)
	}
	return buf.Bytes(), nil
}

// UnpackedPackage — содержимое распакованного ZIP'а с входящим пакетом.
type UnpackedPackage struct {
	DocXML    []byte // первый XML, который не winf.xml и не config.xml
	WinfXML   []byte // транзитный конверт ЭДО (опц., присутствует у входящих от НРД)
	Signature []byte // .sgn файл (отсоединённая подпись), опц.
	Filenames []string
}

// UnpackPackage распаковывает ZIP-архив от ИШ и возвращает структурированно
// тело документа + winf.xml + отсоединённую подпись.
func UnpackPackage(zipBytes []byte) (*UnpackedPackage, error) {
	r, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
	if err != nil {
		return nil, fmt.Errorf("igw: zip reader: %w", err)
	}
	out := &UnpackedPackage{}
	for _, f := range r.File {
		out.Filenames = append(out.Filenames, f.Name)
		rc, err := f.Open()
		if err != nil {
			return nil, fmt.Errorf("igw: open %s in zip: %w", f.Name, err)
		}
		data, err := io.ReadAll(rc)
		rc.Close()
		if err != nil {
			return nil, fmt.Errorf("igw: read %s from zip: %w", f.Name, err)
		}
		low := strings.ToLower(filepath.Base(f.Name))
		switch {
		case low == "winf.xml":
			out.WinfXML = data
		case low == "config.xml":
			// config.xml в исходящих, во входящих обычно отсутствует — игнорируем
		case strings.HasSuffix(low, ".sgn"):
			out.Signature = data
		case strings.HasSuffix(low, ".xml") && out.DocXML == nil:
			out.DocXML = data
		}
	}
	if out.DocXML == nil {
		return nil, fmt.Errorf("igw: в ZIP нет основного .xml документа (файлы: %v)", out.Filenames)
	}
	return out, nil
}

// ParseDecision разбирает DocXML входящего пакета M2MTD в m2m.M2MTransferDecision.
func ParseDecision(docXML []byte) (*m2m.M2MTransferDecision, error) {
	var d m2m.M2MTransferDecision
	if err := nsdxml.Unmarshal(docXML, &d); err != nil {
		return nil, fmt.Errorf("igw: parse M2MTransferDecision: %w", err)
	}
	return &d, nil
}

// ParseResponse разбирает DocXML входящего пакета M2MER в m2m.M2MTransferResponse.
func ParseResponse(docXML []byte) (*m2m.M2MTransferResponse, error) {
	var r m2m.M2MTransferResponse
	if err := nsdxml.Unmarshal(docXML, &r); err != nil {
		return nil, fmt.Errorf("igw: parse M2MTransferResponse: %w", err)
	}
	return &r, nil
}
