//go:build cgo

package parser

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	officeOxide "github.com/yfedoseev/office_oxide/go"
)

// TestPPTXParser_FormatField verifies the format field wiring:
// NewPPTXParser() defaults to "pptx", and an explicit "ppt" can be set.
func TestPPTXParser_FormatField(t *testing.T) {
	p := NewPPTXParser()
	if p.format != "pptx" {
		t.Errorf("NewPPTXParser().format = %q, want %q", p.format, "pptx")
	}
	p2 := &PPTXParser{format: "ppt"}
	if p2.format != "ppt" {
		t.Errorf("explicit PPTXParser{format: \"ppt\"}.format = %q, want %q", p2.format, "ppt")
	}
}

// TestPPTXParser_ParseWithResult_CGO verifies that PPTXParser can
// parse a programmatically generated PPTX document into per-slide
// JSON items. Uses office_oxide's own PptxWriter to produce the
// test data so no external file is needed.
func TestPPTXParser_ParseWithResult_CGO(t *testing.T) {
	ctx := t.Context()
	p := NewPPTXParser()
	data := buildPPTX(t, "Hello World")
	res := p.ParseWithResult(ctx, "test.pptx", data)
	if res.Err != nil {
		t.Fatalf("ParseWithResult: %v", res.Err)
	}
	if res.OutputFormat != "json" {
		t.Errorf("OutputFormat = %q, want %q", res.OutputFormat, "json")
	}
	if got := res.File["format"]; got != "pptx" {
		t.Errorf("File[format] = %v, want %q", got, "pptx")
	}
	if len(res.JSON) == 0 {
		t.Fatal("JSON items is empty; expected at least one slide")
	}
}

// TestPPTParser_ParseWithResult_CGO verifies that PPTParser
// delegates correctly to PPTXParser{format:"ppt"} and produces
// output with File["format"] = "ppt".
func TestPPTParser_ParseWithResult_CGO(t *testing.T) {
	ctx := t.Context()
	p := NewPPTParser()
	// Use PPTX content — office_oxide may reject it with format="ppt"
	// hint (expects OLE binary). When it does, skip gracefully; when
	// it succeeds, verify the metadata contract.
	data := buildPPTX(t, "Hello")
	res := p.ParseWithResult(ctx, "test.ppt", data)
	if res.Err != nil {
		t.Skip("PPTParser with PPTX data (expected maybe to fail):", res.Err)
	}
	if got := res.File["format"]; got != "ppt" {
		t.Errorf("File[format] = %v, want %q", got, "ppt")
	}
}

// TestPPTParser_TCADPFileType verifies that when a .ppt file is routed
// through PPTParser with parse_method="tcadp", the underlying
// PPTXParser must pass "PPT" as the file_type to TCADP, not "PPTX".
// This test verifies format propagation from PPTParser through
// ConfigureFromSetup to the embedded PPTXParser.
func TestPPTParser_TCADPFileType(t *testing.T) {
	// PPTParser delegates to PPTXParser{format:"ppt"}.
	p := NewPPTParser()
	setup := map[string]any{
		"parse_method":  "tcadp",
		"output_format": "json",
	}
	p.ConfigureFromSetup(setup)

	// Verify the embedded PPTXParser received the config and keeps
	// format="ppt" (which maps to "PPT" in TCADP fileType).
	if p.pptx.format != "ppt" {
		t.Errorf("PPTParser.pptx.format = %q, want %q", p.pptx.format, "ppt")
	}
	if p.pptx.ParseMethod != "tcadp" {
		t.Errorf("PPTParser.pptx.ParseMethod = %q, want %q", p.pptx.ParseMethod, "tcadp")
	}
	if p.pptx.OutputFormat != "json" {
		t.Errorf("PPTParser.pptx.OutputFormat = %q, want %q", p.pptx.OutputFormat, "json")
	}
}

// TestPPTXParser_TCADPFileType verifies that a PPTXParser
// with format="pptx" must derive fileType "PPTX" for TCADP calls.
func TestPPTXParser_TCADPFileType(t *testing.T) {
	p := NewPPTXParser()
	if p.format != "pptx" {
		t.Fatalf("NewPPTXParser().format = %q, want pptx", p.format)
	}
	setup := map[string]any{
		"parse_method":  "tcadp",
		"output_format": "json",
	}
	p.ConfigureFromSetup(setup)
	if p.ParseMethod != "tcadp" {
		t.Errorf("ParseMethod = %q, want tcadp", p.ParseMethod)
	}
}

// TestPPTParser_TCADPIntegration drives the end-to-end TCADP path for a
// .ppt file: PPTParser must POST file_type="PPT" to the reconstruct
// endpoint and then process the returned ZIP artifact into JSON items.
func TestPPTParser_TCADPIntegration(t *testing.T) {
	testPresentationTCADPIntegration(t, NewPPTParser(), "PPT", "presentation.ppt")
}

// TestPPTXParser_TCADPIntegration drives the end-to-end TCADP path for a
// .pptx file: PPTXParser must POST file_type="PPTX" to the reconstruct
// endpoint and then process the returned ZIP artifact into JSON items.
func TestPPTXParser_TCADPIntegration(t *testing.T) {
	testPresentationTCADPIntegration(t, NewPPTXParser(), "PPTX", "presentation.pptx")
}

// tcadpPresentationParser is the shared contract of PPTParser and
// PPTXParser used by the TCADP integration helper below.
type tcadpPresentationParser interface {
	ConfigureFromSetup(setup map[string]any)
	ParseWithResult(ctx context.Context, filename string, data []byte) ParseResult
}

func testPresentationTCADPIntegration(t *testing.T, p tcadpPresentationParser, wantFileType, filename string) {
	withSSRFBypass(t)
	t.Helper()
	zipPayload := tcadpZipFixture(t)
	var gotFileType string
	var server *httptest.Server
	server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/reconstruct_document":
			body, err := io.ReadAll(r.Body)
			if err != nil {
				t.Errorf("read reconstruct request: %v", err)
			} else {
				var req struct {
					FileType string `json:"file_type"`
				}
				if err := json.Unmarshal(body, &req); err != nil {
					t.Errorf("decode reconstruct request: %v", err)
				}
				gotFileType = req.FileType
			}
			_, _ = w.Write([]byte(`{"DocumentRecognizeResultUrl":"` + server.URL + `/download.zip"}`))
		case "/download.zip":
			w.Header().Set("Content-Type", "application/zip")
			_, _ = w.Write(zipPayload)
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	p.ConfigureFromSetup(map[string]any{
		"parse_method":    "tcadp",
		"output_format":   "json",
		"tcadp_apiserver": server.URL,
	})
	ctx := t.Context()
	res := p.ParseWithResult(ctx, filename, []byte("dummy-presentation-bytes"))
	if res.Err != nil {
		t.Fatalf("ParseWithResult: %v", res.Err)
	}
	if gotFileType != wantFileType {
		t.Errorf("TCADP request file_type = %q, want %q", gotFileType, wantFileType)
	}
	if res.OutputFormat != "json" {
		t.Errorf("OutputFormat = %q, want json", res.OutputFormat)
	}
	if len(res.JSON) == 0 {
		t.Fatalf("JSON items = 0, want the fixture's parsed content")
	}
}

// TestPPTXParser_TCADPDownloadHTTPError verifies that a non-2xx response
// from the TCADP download endpoint is surfaced as an explicit error rather
// than parsed as a (malformed) ZIP artifact.
func TestPPTXParser_TCADPDownloadHTTPError(t *testing.T) {
	withSSRFBypass(t)
	var server *httptest.Server
	server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch r.URL.Path {
		case "/reconstruct_document":
			_, _ = w.Write([]byte(`{"DocumentRecognizeResultUrl":"` + server.URL + `/download.zip"}`))
		case "/download.zip":
			http.Error(w, "upstream failure", http.StatusInternalServerError)
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	p := NewPPTXParser()
	p.ConfigureFromSetup(map[string]any{
		"parse_method":    "tcadp",
		"output_format":   "json",
		"tcadp_apiserver": server.URL,
	})
	ctx := t.Context()
	res := p.ParseWithResult(ctx, "a.pptx", []byte("dummy"))
	if res.Err == nil {
		t.Fatal("ParseWithResult: want error for non-2xx download, got nil")
	}
	if !strings.Contains(res.Err.Error(), "download HTTP") {
		t.Errorf("err = %v, want error mentioning 'download HTTP'", res.Err)
	}
}

// buildPPTX creates a minimal valid PPTX document with one slide
// containing the given text, using office_oxide's PptxWriter.
func buildPPTX(t *testing.T, text string) []byte {
	t.Helper()
	w := officeOxide.NewPptxWriter()
	slide := w.AddSlide()
	w.SetSlideTitle(slide, text)
	data, err := w.ToBytes()
	if err != nil {
		t.Fatalf("PptxWriter.ToBytes: %v", err)
	}
	return data
}
