package finance

import (
	"context"
	"testing"

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/uploads"
)

// The proof read path and the proof write path must apply ONE rule: the owning
// buyer (payment -> invoice -> customers.user_id) or staff holding
// ProofVerifyPermission. If these two drift, a proof becomes readable or
// unwritable by the wrong identity. Role->glob resolution is DB-driven and
// covered by the auth package, so the grants asserted here are explicit
// permission overrides.
func TestCanAccessPaymentProof(t *testing.T) {
	const ownerID int64 = 7
	cases := []struct {
		name     string
		user     *auth.User
		owner    int64
		wantRead bool
	}{
		{name: "owner may read", user: &auth.User{ID: ownerID}, owner: ownerID, wantRead: true},
		{name: "non-owner without permission is denied", user: &auth.User{ID: 8}, owner: ownerID, wantRead: false},
		{
			name:  "non-owner with verify permission may read",
			user:  &auth.User{ID: 8, Permissions: auth.Permissions{ProofVerifyPermission: true}},
			owner: ownerID, wantRead: true,
		},
		{name: "proof with no linked customer user denies the owner branch", user: &auth.User{ID: 8}, owner: 0, wantRead: false},
		{
			name:  "proof with no linked customer user is readable by staff",
			user:  &auth.User{ID: 8, Permissions: auth.Permissions{ProofVerifyPermission: true}},
			owner: 0, wantRead: true,
		},
		{name: "anonymous is denied", user: nil, owner: ownerID, wantRead: false},
		{name: "admin bypasses", user: &auth.User{ID: 1, Role: auth.RoleAdmin}, owner: ownerID, wantRead: true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if got := CanAccessPaymentProof(tc.user, tc.owner); got != tc.wantRead {
				t.Fatalf("CanAccessPaymentProof = %v, want %v", got, tc.wantRead)
			}
		})
	}
}

// The payments table is the source of truth for "is this stored key a private
// payment proof, and who owns it" — including keys written before proofs were
// namespaced, which carry no marker at all.
func TestPaymentProofKeyOwner_ResolvesLegacyAndPrefixedKeys(t *testing.T) {
	const ownerID int64 = 7
	const legacyKey = "99887766554433221100ffeeddccbbaa.png"
	const prefixedKey = uploads.ProofPrefix + "aabbccddeeff00112233445566778899.png"
	const ownerlessKey = "ffeeddccbbaa99887766554433221100.png"

	cases := []struct {
		name     string
		proofURL string // stored on the payment row, "" = no proof
		key      string
		wantOwn  int64
		wantPf   bool
	}{
		{name: "legacy single-segment proof URL resolves to its owner", proofURL: "/uploads/" + legacyKey, key: legacyKey, wantOwn: ownerID, wantPf: true},
		{name: "bare legacy key in proof_url also matches", proofURL: legacyKey, key: legacyKey, wantOwn: ownerID, wantPf: true},
		{name: "proof-prefixed key resolves to its owner", proofURL: "/uploads/" + prefixedKey, key: prefixedKey, wantOwn: ownerID, wantPf: true},
		{name: "proof with no linked customer user has no owner", proofURL: "/uploads/" + ownerlessKey, key: ownerlessKey, wantOwn: 0, wantPf: true},
		{name: "unrelated key is not a proof", proofURL: "/uploads/" + legacyKey, key: "0f1e2d3c4b5a69788796a5b4c3d2e1f0.png", wantPf: false},
		{name: "payment without a proof is not a proof", key: legacyKey, wantPf: false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			s := svc()
			st := s.store.(*fakeStore)
			st.payments[1] = &Payment{ID: 1, PaymentNumber: "PAY-1", PayableType: InvoiceModel, PayableID: 3}
			if tc.proofURL != "" {
				url := tc.proofURL
				st.payments[1].ProofURL = &url
				st.paymentOwners[1] = ownerID
				if tc.wantOwn == 0 {
					delete(st.paymentOwners, 1) // customer without a linked user
				}
			}
			owner, isProof, err := s.PaymentProofKeyOwner(context.Background(), tc.key)
			if err != nil {
				t.Fatalf("PaymentProofKeyOwner: %v", err)
			}
			if isProof != tc.wantPf || owner != tc.wantOwn {
				t.Fatalf("PaymentProofKeyOwner = (owner %d, isProof %v), want (owner %d, isProof %v)", owner, isProof, tc.wantOwn, tc.wantPf)
			}
		})
	}
}

// Service.ProofAccess is the injected read-path policy: public images pass
// through untouched, legacy proofs are gated by the table lookup, and a
// proof-prefixed key stays private even when no payment row resolves for it.
func TestServiceProofAccess(t *testing.T) {
	const ownerID int64 = 7
	const legacyKey = "99887766554433221100ffeeddccbbaa.png"
	const orphanPrefixedKey = uploads.ProofPrefix + "0011223344556677889900aabbccdd.png"

	owner := &auth.User{ID: ownerID}
	verifier := &auth.User{ID: 9, Permissions: auth.Permissions{ProofVerifyPermission: true}}
	productImage := "0f1e2d3c4b5a69788796a5b4c3d2e1f0.png"

	newSvc := func(t *testing.T) *Service {
		t.Helper()
		s := svc()
		st := s.store.(*fakeStore)
		st.payments[1] = &Payment{ID: 1, PaymentNumber: "PAY-1", PayableType: InvoiceModel, PayableID: 3}
		url := "/uploads/" + legacyKey
		st.payments[1].ProofURL = &url
		st.paymentOwners[1] = ownerID
		return s
	}

	cases := []struct {
		name        string
		key         string
		user        *auth.User
		wantPrivate bool
		wantAllowed bool
	}{
		{name: "product image stays public", key: productImage, user: nil},
		{name: "product image stays public for a stranger", key: productImage, user: verifier},
		{name: "legacy proof is private", key: legacyKey, user: verifier, wantPrivate: true, wantAllowed: true},
		{name: "legacy proof is denied to a non-owner", key: legacyKey, user: &auth.User{ID: 8}, wantPrivate: true},
		{name: "legacy proof is denied to its owner when anonymous", key: legacyKey, wantPrivate: true},
		{name: "legacy proof is allowed to its owner", key: legacyKey, user: owner, wantPrivate: true, wantAllowed: true},
		{name: "orphan proof-prefixed key stays private and is denied without an owner", key: orphanPrefixedKey, user: &auth.User{ID: 8}, wantPrivate: true},
		{name: "orphan proof-prefixed key stays private but staff may read it", key: orphanPrefixedKey, user: verifier, wantPrivate: true, wantAllowed: true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			access, err := newSvc(t).ProofAccess(context.Background(), tc.key, tc.user)
			if err != nil {
				t.Fatalf("ProofAccess: %v", err)
			}
			if access.Private != tc.wantPrivate || access.Allowed != tc.wantAllowed {
				t.Fatalf("ProofAccess = %+v, want {Private:%v Allowed:%v}", access, tc.wantPrivate, tc.wantAllowed)
			}
		})
	}
}
