package main

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

	"sub2api-cn-relay-manager/internal/config"
	"sub2api-cn-relay-manager/internal/overlay"
	"sub2api-cn-relay-manager/internal/pack"
	"sub2api-cn-relay-manager/internal/provision"
	"sub2api-cn-relay-manager/internal/reconcile"
	"sub2api-cn-relay-manager/internal/store/sqlite"
)

type errWriter struct {
	err error
}

func (w errWriter) Write([]byte) (int, error) {
	return 0, w.err
}

func TestExecuteWritesConfigSummaryAfterBootstrap(t *testing.T) {
	var output bytes.Buffer
	loadCalled := false

	err := execute(context.Background(), &output, nil, func(context.Context) (config.StartupConfig, error) {
		loadCalled = true
		return config.StartupConfig{
			Server: config.ServerConfig{ListenAddr: ":9191"},
			Database: config.DatabaseConfig{
				SQLiteDSN: "file:test.db?_foreign_keys=on",
			},
		}, nil
	}, nil, nil, nil, nil, nil, nil, nil)
	if err != nil {
		t.Fatalf("execute() returned error: %v", err)
	}

	if !loadCalled {
		t.Fatal("execute() did not load config")
	}

	got := output.String()
	if !strings.Contains(got, "sub2api-cn-relay-manager cli ready") {
		t.Fatalf("execute() output = %q, want readiness line", got)
	}

	if !strings.Contains(got, "listen_addr=:9191") {
		t.Fatalf("execute() output = %q, want listen addr summary", got)
	}

	if !strings.Contains(got, "sqlite_dsn=file:test.db?_foreign_keys=on") {
		t.Fatalf("execute() output = %q, want sqlite dsn summary", got)
	}
}

func TestExecuteReturnsBootstrapError(t *testing.T) {
	wantErr := errors.New("load config failed")

	err := execute(context.Background(), &bytes.Buffer{}, nil, func(context.Context) (config.StartupConfig, error) {
		return config.StartupConfig{}, wantErr
	}, nil, nil, nil, nil, nil, nil, nil)
	if !errors.Is(err, wantErr) {
		t.Fatalf("execute() error = %v, want %v", err, wantErr)
	}
}

func TestExecuteReturnsWriteError(t *testing.T) {
	wantErr := errors.New("write failed")

	err := execute(context.Background(), errWriter{err: wantErr}, nil, func(context.Context) (config.StartupConfig, error) {
		return config.StartupConfig{
			Server:   config.ServerConfig{ListenAddr: ":9292"},
			Database: config.DatabaseConfig{SQLiteDSN: "file:test.db"},
		}, nil
	}, nil, nil, nil, nil, nil, nil, nil)
	if !errors.Is(err, wantErr) {
		t.Fatalf("execute() error = %v, want %v", err, wantErr)
	}
}

func TestExecuteInstallPackWritesSummary(t *testing.T) {
	var output bytes.Buffer
	installCalled := false

	err := execute(context.Background(), &output, []string{
		"install-pack",
		"--host-base-url", "https://sub2api.example.com",
		"--pack-path", "/tmp/openai-pack.zip",
	}, nil, func(_ context.Context, req installPackCLIRequest) (provision.PackInstallResult, error) {
		installCalled = true
		if req.PackPath != "/tmp/openai-pack.zip" {
			t.Fatalf("unexpected install request: %+v", req)
		}
		return provision.PackInstallResult{
			Pack:        sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0"},
			HostVersion: "0.1.126",
			Providers:   []sqlite.Provider{{ProviderID: "deepseek"}},
		}, nil
	}, nil, nil, nil, nil, nil, nil)
	if err != nil {
		t.Fatalf("execute() install-pack error = %v", err)
	}
	if !installCalled {
		t.Fatal("execute() did not invoke installPack")
	}
	got := output.String()
	if !strings.Contains(got, "pack_id=openai-cn-pack") || !strings.Contains(got, "providers=1") || !strings.Contains(got, "host_version=0.1.126") {
		t.Fatalf("execute() install-pack output = %q, want summary", got)
	}
}

func TestExecuteImportProviderWritesSummary(t *testing.T) {
	var output bytes.Buffer
	importCalled := false

	err := execute(context.Background(), &output, []string{
		"import-provider",
		"--host-base-url", "https://sub2api.example.com",
		"--pack-dir", "/tmp/pack",
		"--provider-id", "deepseek",
		"--keys", "k1,k2",
		"--access-api-key", "user-key",
	}, nil, nil, func(_ context.Context, req importCLIRequest) (provision.ImportReport, error) {
		importCalled = true
		if req.ProviderID != "deepseek" || len(req.Keys) != 2 {
			t.Fatalf("unexpected import request: %+v", req)
		}
		return provision.ImportReport{
			BatchStatus:    provision.BatchStatusSucceeded,
			ProviderStatus: provision.ProviderStatusActive,
			AccessStatus:   provision.AccessStatusSelfServiceReady,
			Accounts:       []provision.AccountImportResult{{}, {}},
		}, nil
	}, nil, nil, nil, nil, nil)
	if err != nil {
		t.Fatalf("execute() import error = %v", err)
	}
	if !importCalled {
		t.Fatal("execute() did not invoke importProvider")
	}
	got := output.String()
	if !strings.Contains(got, "batch_status=succeeded") || !strings.Contains(got, "accounts=2") {
		t.Fatalf("execute() import output = %q, want summary", got)
	}
}

func TestExecutePreviewProviderWritesSummary(t *testing.T) {
	var output bytes.Buffer
	previewCalled := false

	err := execute(context.Background(), &output, []string{
		"preview-provider",
		"--host-base-url", "https://sub2api.example.com",
		"--pack-dir", "/tmp/pack",
		"--provider-id", "deepseek",
		"--keys", "k1,k2",
	}, nil, nil, nil, func(_ context.Context, req previewCLIRequest) (provision.PreviewReport, error) {
		previewCalled = true
		if req.ProviderID != "deepseek" || len(req.Keys) != 2 {
			t.Fatalf("unexpected preview request: %+v", req)
		}
		return provision.PreviewReport{
			AcceptedKeys: []string{"k1", "k2"},
			Names:        provision.ResourceNames{Group: "crm-deepseek-group", Channel: "crm-deepseek-channel", Plan: "crm-deepseek-plan"},
			Decisions: map[string]provision.PreviewDecision{
				"group":   {Action: provision.PreviewActionCreate},
				"channel": {Action: provision.PreviewActionReuse, ExistingID: "channel_1"},
				"plan":    {Action: provision.PreviewActionConflict},
			},
		}, nil
	}, nil, nil, nil, nil)
	if err != nil {
		t.Fatalf("execute() preview error = %v", err)
	}
	if !previewCalled {
		t.Fatal("execute() did not invoke previewProvider")
	}
	got := output.String()
	if !strings.Contains(got, "accepted_keys=2") || !strings.Contains(got, "group=create") || !strings.Contains(got, "channel=reuse") || !strings.Contains(got, "plan=conflict") {
		t.Fatalf("execute() preview output = %q, want summary", got)
	}
}

func TestExecuteRollbackProviderWritesSummary(t *testing.T) {
	var output bytes.Buffer
	rollbackCalled := false

	err := execute(context.Background(), &output, []string{
		"rollback-provider",
		"--host-base-url", "https://sub2api.example.com",
		"--pack-dir", "/tmp/pack",
		"--provider-id", "deepseek",
	}, nil, nil, nil, nil, func(_ context.Context, req rollbackCLIRequest) (rollbackSummary, error) {
		rollbackCalled = true
		if req.ProviderID != "deepseek" {
			t.Fatalf("unexpected rollback request: %+v", req)
		}
		return rollbackSummary{Accounts: 2, Plans: 1, Channels: 1, Groups: 1}, nil
	}, nil, nil, nil)
	if err != nil {
		t.Fatalf("execute() rollback error = %v", err)
	}
	if !rollbackCalled {
		t.Fatal("execute() did not invoke rollbackProvider")
	}
	got := output.String()
	if !strings.Contains(got, "deleted_accounts=2") || !strings.Contains(got, "deleted_groups=1") {
		t.Fatalf("execute() rollback output = %q, want summary", got)
	}
}

func TestExecuteReconcileProviderWritesSummary(t *testing.T) {
	var output bytes.Buffer
	reconcileCalled := false

	err := execute(context.Background(), &output, []string{
		"reconcile-provider",
		"--host-base-url", "https://sub2api.example.com",
		"--pack-dir", "/tmp/pack",
		"--provider-id", "deepseek",
		"--access-api-key", "user-key",
	}, nil, nil, nil, nil, nil, func(_ context.Context, req reconcileCLIRequest) (reconcile.Result, error) {
		reconcileCalled = true
		if req.ProviderID != "deepseek" || req.AccessAPIKey != "user-key" {
			t.Fatalf("unexpected reconcile request: %+v", req)
		}
		return reconcile.Result{Status: "drifted", MissingCount: 1, ExtraCount: 2, ProbeFailureCount: 1, AccessStatus: provision.AccessStatusBroken}, nil
	}, nil, nil)
	if err != nil {
		t.Fatalf("execute() reconcile error = %v", err)
	}
	if !reconcileCalled {
		t.Fatal("execute() did not invoke reconcileProvider")
	}
	got := output.String()
	if !strings.Contains(got, "status=drifted") || !strings.Contains(got, "missing_count=1") || !strings.Contains(got, "access_status=broken") {
		t.Fatalf("execute() reconcile output = %q, want summary", got)
	}
}

func TestExecuteApplyHostOverlayWritesSummary(t *testing.T) {
	var output bytes.Buffer
	applyCalled := false

	err := execute(context.Background(), &output, []string{
		"apply-host-overlay",
		"--pack-dir", "/tmp/pack",
		"--provider-id", "kimi-a7m",
		"--host-version", "0.1.129",
		"--source-dir", "/tmp/sub2api-src",
	}, nil, nil, nil, nil, nil, nil, func(_ context.Context, req applyHostOverlayCLIRequest) (overlay.ApplyResult, error) {
		applyCalled = true
		if req.ProviderID != "kimi-a7m" || req.HostVersion != "0.1.129" {
			t.Fatalf("unexpected apply-host-overlay request: %+v", req)
		}
		return overlay.ApplyResult{
			OutputDir: "/tmp/sub2api-src-patched-sub2api-stock-v0129-kimi-a7m",
			AppliedOverlays: []pack.HostOverlay{{
				OverlayID: "sub2api-stock-v0129-kimi-a7m",
			}},
			MetadataFilePath: "/tmp/sub2api-src-patched-sub2api-stock-v0129-kimi-a7m/.sub2api-cn-relay-manager-overlay.json",
		}, nil
	}, nil)
	if err != nil {
		t.Fatalf("execute() apply-host-overlay error = %v", err)
	}
	if !applyCalled {
		t.Fatal("execute() did not invoke applyHostOverlay")
	}
	got := output.String()
	if !strings.Contains(got, "applied_overlays=1") || !strings.Contains(got, "overlay_ids=sub2api-stock-v0129-kimi-a7m") {
		t.Fatalf("execute() apply-host-overlay output = %q, want overlay summary", got)
	}
}

func TestParseApplyHostOverlayCLIArgsRequiresSourceDir(t *testing.T) {
	_, err := parseApplyHostOverlayCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "kimi-a7m", "--host-version", "0.1.129"})
	if err == nil {
		t.Fatal("parseApplyHostOverlayCLIArgs() error = nil, want validation error")
	}
}

func TestParseInstallPackCLIArgsRequiresHostBaseURL(t *testing.T) {
	_, err := parseInstallPackCLIArgs([]string{"--pack-path", "/tmp/openai-pack.zip"})
	if err == nil {
		t.Fatal("parseInstallPackCLIArgs() error = nil, want validation error")
	}
}

func TestParseImportCLIArgsRequiresHostBaseURL(t *testing.T) {
	_, err := parseImportCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek", "--keys", "k1", "--access-api-key", "user-key"})
	if err == nil {
		t.Fatal("parseImportCLIArgs() error = nil, want validation error")
	}
}

func TestParsePreviewCLIArgsRequiresHostBaseURL(t *testing.T) {
	_, err := parsePreviewCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek", "--keys", "k1"})
	if err == nil {
		t.Fatal("parsePreviewCLIArgs() error = nil, want validation error")
	}
}

func TestParseRollbackCLIArgsRequiresHostBaseURL(t *testing.T) {
	_, err := parseRollbackCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek"})
	if err == nil {
		t.Fatal("parseRollbackCLIArgs() error = nil, want validation error")
	}
}

func TestParseReconcileCLIArgsRequiresHostBaseURL(t *testing.T) {
	_, err := parseReconcileCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek"})
	if err == nil {
		t.Fatal("parseReconcileCLIArgs() error = nil, want validation error")
	}
}
