package control

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"
)

func TestDBTAPIBaseSupportsAccountCellHosts(t *testing.T) {
	for _, host := range []string{"cloud.getdbt.com", "emea.dbt.com", "au.dbt.com", "ft490.us1.dbt.com", "example.us2.dbt.com", "example.us3.dbt.com", "example.us5.dbt.com", "example.eu1.dbt.com", "example.eu2.dbt.com", "example.eu3.dbt.com", "example.eu4.dbt.com", "example.au1.dbt.com", "example.au2.dbt.com", "example.jp1.dbt.com"} {
		t.Run(host, func(t *testing.T) {
			got, err := validatedDBTAPIBase("https://" + host + "/")
			if err != nil || got != "https://"+host {
				t.Fatalf("valid account URL rejected: %q %v", got, err)
			}
		})
	}
}

func TestDBTAPIBaseRejectsCredentialExfiltrationAndUnknownRegions(t *testing.T) {
	for _, base := range []string{
		"http://ft490.us1.dbt.com", "https://ft490.us1.dbt.com.evil.test", "https://evil.test",
		"https://ft490.us1.dbt.com@evil.test", "https://actor@ft490.us1.dbt.com",
		"https://ft490.us1.dbt.com:8443", "https://ft490.us1.dbt.com/api/v2", "https://ft490.us1.dbt.com/%2f",
		"https://ft490.us1.dbt.com/?token=x", "https://ft490.us1.dbt.com/?", "https://ft490.us1.dbt.com/#fragment",
		"https://us1.dbt.com", "https://ft490.us9.dbt.com", "https://nested.ft490.us1.dbt.com", "https://-bad.us1.dbt.com",
		"https://ft490.us1.dbt.com.", "https://marketing.getdbt.com", "https://127.0.0.1",
	} {
		t.Run(base, func(t *testing.T) {
			if _, err := validatedDBTAPIBase(base); err == nil {
				t.Fatal("invalid API URL accepted")
			}
		})
	}
}

func TestDBTHistoryUsesFullAccountHostAndExactJob(t *testing.T) {
	t.Setenv("FINANCE_PORTAL_DBT_ACCOUNT_ID", "85341")
	t.Setenv("FINANCE_PORTAL_DBT_JOB_ID", "456")
	t.Setenv("FINANCE_PORTAL_DBT_API_BASE", "https://ft490.us1.dbt.com/")
	t.Setenv("DBT_CLOUD_API_TOKEN", "fixture-token-not-for-output")
	now := time.Now().UTC().Add(-time.Hour).Format("2006-01-02 15:04:05.999999999+00:00")
	calls := 0
	s := &ExternalLiveSource{httpClient: &http.Client{Transport: testTransport(func(r *http.Request) (*http.Response, error) {
		calls++
		if r.Method != http.MethodGet || r.URL.Host != "ft490.us1.dbt.com" || r.URL.Path != "/api/v2/accounts/85341/runs/" || r.URL.Query().Get("job_definition_id") != "456" || r.URL.Query().Get("offset") != "100" || r.Header.Get("Authorization") != "Token fixture-token-not-for-output" {
			t.Error("incorrect account/job read binding")
		}
		body := fmt.Sprintf(`{"status":{"is_success":true},"data":[{"id":123,"job_definition_id":456,"created_at":%q,"started_at":%q,"finished_at":%q,"status":20,"status_humanized":"sensitive provider detail"}]}`, now, now, now)
		return &http.Response{StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: http.Header{}}, nil
	})}}
	b, err := s.dbtHistory(context.Background(), CollectionState{Cursor: "100"})
	if err != nil || calls != 1 || len(b.Runs) != 1 {
		t.Fatalf("history read failed: %v calls=%d", err, calls)
	}
	if b.Runs[0].ExternalID != "85341:123" || b.Runs[0].Status != "failed" || b.Runs[0].Verification != "not_checked" || strings.Contains(b.Runs[0].Message, "sensitive") || b.State.Status != "partial" {
		t.Fatal("execution, verification or sanitization boundary lost")
	}
	t.Setenv("FINANCE_PORTAL_DBT_API_BASE", "https://ft490.us1.dbt.com.evil.test")
	if _, err := s.dbtHistory(context.Background(), CollectionState{}); err == nil || calls != 1 {
		t.Fatal("token request issued to an invalid host")
	}
}

func TestDBTTimestampsHandleActualAPIAndRejectUnzonedValues(t *testing.T) {
	for _, value := range []string{"2026-09-08T00:00:01Z", "2026-09-08 00:00:01+00:00", "2026-09-08 08:00:01+08:00", "2026-09-08T00:00:01.123456Z"} {
		var stamp dbtTimestamp
		body, _ := json.Marshal(value)
		if err := json.Unmarshal(body, &stamp); err != nil || stamp.Time.UTC().Format("2006-01-02T15:04:05Z") != "2026-09-08T00:00:01Z" {
			t.Fatalf("actual dbt timestamp rejected or shifted: %q %v", value, err)
		}
	}
	for _, value := range []string{`"2026-09-08 00:00:01"`, `"2026-09-08"`, `1234`, `{}`, `""`, `"0001-01-01T00:00:00Z"`} {
		var stamp dbtTimestamp
		if err := json.Unmarshal([]byte(value), &stamp); err == nil {
			t.Fatalf("invalid dbt timestamp accepted: %s", value)
		}
	}
	var nullable struct {
		Finished *dbtTimestamp `json:"finished_at"`
	}
	if err := json.Unmarshal([]byte(`{"finished_at":null}`), &nullable); err != nil || nullable.Finished != nil {
		t.Fatal("unfinished run should retain a missing finish time")
	}
}

// Explicit opt-in only. Credentials stay in the invoking process environment;
// this reads one configured Job and neither dispatches nor persists its runs.
func TestDBTHistoryLiveReadOnly(t *testing.T) {
	if os.Getenv("FINANCE_PORTAL_VERIFY_DBT_HISTORY") != "1" {
		t.Skip("live read-only acceptance requires explicit opt-in")
	}
	expected := os.Getenv("FINANCE_PORTAL_VERIFY_DBT_RUN_ID")
	if expected == "" || os.Getenv("FINANCE_PORTAL_DBT_JOB_ID") == "" || os.Getenv("DBT_CLOUD_API_TOKEN") == "" {
		t.Fatal("exact Job, observed run and API identity are required")
	}
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()
	s := &ExternalLiveSource{httpClient: &http.Client{Timeout: 25 * time.Second, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }}}
	b, err := s.dbtHistory(ctx, CollectionState{})
	if err != nil {
		t.Fatalf("bounded history read failed: %v", err)
	}
	found := false
	for _, run := range b.Runs {
		if run.Verification != "not_checked" || run.Provider != "dbt" || run.StartedAt.IsZero() {
			t.Fatal("history contract violated")
		}
		if run.ExternalID == os.Getenv("FINANCE_PORTAL_DBT_ACCOUNT_ID")+":"+expected {
			found = true
			if run.Status != os.Getenv("FINANCE_PORTAL_VERIFY_DBT_RUN_STATUS") {
				t.Fatal("observed execution result differs from independent API read")
			}
			t.Logf("verified run=%s status=%s started=%s; business verification=not_checked", run.ExternalID, run.Status, run.StartedAt.Format(time.RFC3339))
		}
	}
	if !found || b.State.Status != "partial" {
		t.Fatal("previously observed run missing or coverage overstated")
	}
	t.Logf("bounded history rows=%d", len(b.Runs))
}
