package handler

import (
	"baron-sso-backend/internal/domain"
	"bytes"
	"context"
	"encoding/json"
	"io"
	"net/http"
	"time"
)

// --- Mock IDP Provider ---

type mockIdpProvider struct {
	userExists      bool
	name            string
	signInInfo      *domain.AuthInfo
	issueSession    *domain.AuthInfo
	verifyCodeInfo  *domain.AuthInfo
	err             error
	initiateLinkErr error
	updateCalled    bool
	updateCallCount int
	updatedLoginID  string
	updatedPassword string
}

func (m *mockIdpProvider) Name() string {
	if m.name != "" {
		return m.name
	}
	return "mock-idp"
}

func (m *mockIdpProvider) GetMetadata() (*domain.IDPMetadata, error) { return nil, m.err }
func (m *mockIdpProvider) CreateUser(user *domain.BrokerUser, password string) (string, error) {
	return "mock-user-id", m.err
}

func (m *mockIdpProvider) SignIn(loginID, password string) (*domain.AuthInfo, error) {
	return m.signInInfo, m.err
}
func (m *mockIdpProvider) UserExists(loginID string) (bool, error) { return m.userExists, m.err }
func (m *mockIdpProvider) IssueSession(loginID string) (*domain.AuthInfo, error) {
	if m.issueSession != nil {
		return m.issueSession, m.err
	}
	return &domain.AuthInfo{
		SessionToken: &domain.Token{JWT: "valid-jwt", SessionID: "valid-sid"},
	}, m.err
}

func (m *mockIdpProvider) InitiateLinkLogin(loginID, returnTo string) (*domain.LinkLoginInit, error) {
	if m.initiateLinkErr != nil {
		return nil, m.initiateLinkErr
	}
	return &domain.LinkLoginInit{FlowID: "mock-flow-id", Mode: "code"}, m.err
}

func (m *mockIdpProvider) VerifyLoginCode(loginID, flowID, code string) (*domain.AuthInfo, error) {
	return m.verifyCodeInfo, m.err
}
func (m *mockIdpProvider) GetPasswordPolicy() (*domain.PasswordPolicy, error)      { return nil, m.err }
func (m *mockIdpProvider) InitiatePasswordReset(loginID, redirectUrl string) error { return m.err }
func (m *mockIdpProvider) VerifyPasswordResetToken(token string) (*domain.AuthInfo, error) {
	return nil, m.err
}

func (m *mockIdpProvider) UpdateUserPassword(loginID, newPassword string, r *http.Request) error {
	m.updateCalled = true
	m.updateCallCount++
	m.updatedLoginID = loginID
	m.updatedPassword = newPassword
	return m.err
}

// --- Mock Audit Repository ---

type mockAuditRepo struct {
	logs []domain.AuditLog
}

func (m *mockAuditRepo) Create(log *domain.AuditLog) error {
	m.logs = append(m.logs, *log)
	return nil
}

func (m *mockAuditRepo) FindPage(ctx context.Context, limit int, cursor *domain.AuditCursor, tenantID string) ([]domain.AuditLog, error) {
	return m.logs, nil
}

func (m *mockAuditRepo) FindByUserAndEvents(ctx context.Context, userID string, eventTypes []string, limit int) ([]domain.AuditLog, error) {
	var results []domain.AuditLog
	for _, log := range m.logs {
		if log.UserID == userID {
			for _, et := range eventTypes {
				if log.EventType == et {
					results = append(results, log)
					break
				}
			}
		}
	}
	return results, nil
}

func (m *mockAuditRepo) CountFailuresSince(ctx context.Context, since time.Time, tenantID string) (int64, error) {
	return 0, nil
}

func (m *mockAuditRepo) CountEventsSince(ctx context.Context, since time.Time) (int64, error) {
	return 0, nil
}

func (m *mockAuditRepo) CountActiveSessionsSince(ctx context.Context, since time.Time, tenantID string) (int64, error) {
	return 0, nil
}

func (m *mockAuditRepo) Ping(ctx context.Context) error { return nil }

type mockRPUsageEventSink struct {
	events []domain.RPUsageEvent
	err    error
}

func (m *mockRPUsageEventSink) EmitRPUsageEvent(ctx context.Context, event domain.RPUsageEvent) error {
	if m.err != nil {
		return m.err
	}
	m.events = append(m.events, event)
	return nil
}

type mockOathkeeperRepo struct {
	logs []domain.OathkeeperAccessLog
}

func (m *mockOathkeeperRepo) FindPageBySubject(ctx context.Context, subject string, limit int, cursor *domain.AuditCursor) ([]domain.OathkeeperAccessLog, error) {
	if subject == "" {
		return m.logs, nil
	}
	results := make([]domain.OathkeeperAccessLog, 0, len(m.logs))
	for _, log := range m.logs {
		if log.Subject == subject {
			results = append(results, log)
		}
	}
	return results, nil
}

func (m *mockOathkeeperRepo) Ping(ctx context.Context) error { return nil }

// --- Mock Consent Repository ---

type mockConsentRepo struct {
	consents []domain.ClientConsent
}

func (m *mockConsentRepo) Upsert(ctx context.Context, consent *domain.ClientConsent) error {
	m.consents = append(m.consents, *consent)
	return nil
}

func (m *mockConsentRepo) ListBySubject(ctx context.Context, subject string) ([]domain.ClientConsent, error) {
	var results []domain.ClientConsent
	for _, c := range m.consents {
		if c.Subject == subject {
			results = append(results, c)
		}
	}
	return results, nil
}

func (m *mockConsentRepo) ListSubjectsByClient(ctx context.Context, clientID string) ([]string, error) {
	seen := map[string]struct{}{}
	subjects := make([]string, 0, len(m.consents))
	for _, consent := range m.consents {
		if consent.ClientID != clientID {
			continue
		}
		if _, ok := seen[consent.Subject]; ok {
			continue
		}
		seen[consent.Subject] = struct{}{}
		subjects = append(subjects, consent.Subject)
	}
	return subjects, nil
}

func (m *mockConsentRepo) Find(ctx context.Context, clientID, subject string) (*domain.ClientConsent, error) {
	for _, consent := range m.consents {
		if consent.ClientID == clientID && consent.Subject == subject {
			found := consent
			return &found, nil
		}
	}
	return nil, nil
}

func (m *mockConsentRepo) Delete(ctx context.Context, subject, clientID string) error { return nil }

func (m *mockConsentRepo) DeleteByClient(ctx context.Context, clientID string) error {
	filtered := m.consents[:0]
	for _, consent := range m.consents {
		if consent.ClientID != clientID {
			filtered = append(filtered, consent)
		}
	}
	m.consents = filtered
	return nil
}

func (m *mockConsentRepo) List(ctx context.Context, clientID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error) {
	results := make([]domain.ClientConsentWithTenantInfo, 0, len(m.consents))
	for _, consent := range m.consents {
		if consent.ClientID == clientID {
			results = append(results, domain.ClientConsentWithTenantInfo{ClientConsent: consent})
		}
	}
	return results, int64(len(results)), nil
}

func (m *mockConsentRepo) ListByTenant(ctx context.Context, clientID, tenantID string, limit, offset int) ([]domain.ClientConsentWithTenantInfo, int64, error) {
	results := make([]domain.ClientConsentWithTenantInfo, 0, len(m.consents))
	for _, consent := range m.consents {
		if consent.ClientID == clientID {
			results = append(results, domain.ClientConsentWithTenantInfo{
				ClientConsent: consent,
				TenantID:      tenantID,
			})
		}
	}
	return results, int64(len(results)), nil
}

// --- Mock Secret Repository ---

type mockSecretRepo struct {
	secrets map[string]string
}

func (m *mockSecretRepo) Upsert(ctx context.Context, clientID, secret string) error {
	if m.secrets == nil {
		m.secrets = make(map[string]string)
	}
	m.secrets[clientID] = secret
	return nil
}

func (m *mockSecretRepo) GetByID(ctx context.Context, clientID string) (string, error) {
	return m.secrets[clientID], nil
}

func (m *mockSecretRepo) Delete(ctx context.Context, clientID string) error {
	delete(m.secrets, clientID)
	return nil
}

// --- HTTP Mock Helpers ---

type roundTripFunc func(req *http.Request) (*http.Response, error)

func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
	return f(req)
}

func setDefaultHTTPClientForTest(t interface{ Cleanup(func()) }, transport http.RoundTripper) {
	origDefault := http.DefaultClient
	http.DefaultClient = &http.Client{Transport: transport}
	t.Cleanup(func() {
		http.DefaultClient = origDefault
	})
}

func httpResponse(r *http.Request, code int, body string) *http.Response {
	return &http.Response{
		StatusCode: code,
		Header:     make(http.Header),
		Body:       io.NopCloser(bytes.NewBufferString(body)),
		Request:    r,
	}
}

func httpJSONAny(r *http.Request, code int, data any) *http.Response {
	body, _ := json.Marshal(data)
	return &http.Response{
		StatusCode: code,
		Header: http.Header{
			"Content-Type": []string{"application/json"},
		},
		Body:    io.NopCloser(bytes.NewBuffer(body)),
		Request: r,
	}
}
