package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/domain"
)

// mockDomainService implements DomainService for testing.
type mockDomainService struct {
	domains map[string][]*domain.ProjectDomain // projectID -> domains
	err     error
}

func newMockDomainService() *mockDomainService {
	return &mockDomainService{
		domains: make(map[string][]*domain.ProjectDomain),
	}
}

func (m *mockDomainService) ListDomains(ctx context.Context, projectID string) ([]*domain.ProjectDomain, error) {
	if m.err != nil {
		return nil, m.err
	}
	return m.domains[projectID], nil
}

func (m *mockDomainService) AddDomain(ctx context.Context, req DomainAddRequest) (*domain.ProjectDomain, error) {
	if m.err != nil {
		return nil, m.err
	}
	pd := &domain.ProjectDomain{
		ID:            int64(len(m.domains[req.ProjectID]) + 1),
		ProjectID:     req.ProjectID,
		Domain:        req.Domain,
		Type:          req.Type,
		DNSRecordType: req.RecordType,
		Verified:      true,
	}
	m.domains[req.ProjectID] = append(m.domains[req.ProjectID], pd)
	return pd, nil
}

func (m *mockDomainService) RemoveDomain(ctx context.Context, projectID, fqdn string) error {
	if m.err != nil {
		return m.err
	}
	domains := m.domains[projectID]
	for i, d := range domains {
		if d.Domain == fqdn {
			// Check if it's the primary auto domain
			if d.Type == domain.DomainTypePrimaryAuto {
				return domain.ErrDomainNotFound // Mimic primary domain protection
			}
			m.domains[projectID] = append(domains[:i], domains[i+1:]...)
			return nil
		}
	}
	return domain.ErrDomainNotFound
}

func setupInfraDomainHandler() (*InfrastructureHandler, *mockDomainService, chi.Router) {
	domainSvc := newMockDomainService()
	h := NewInfrastructureHandler(nil, nil, nil, nil, nil, domainSvc, InfrastructureConfig{
		DefaultGitOwner: "threesix",
		DefaultDomain:   "threesix.ai",
		ClusterIP:       "208.122.204.172",
	})
	r := chi.NewRouter()
	r.Use(testAdminAuth)
	h.Mount(r)
	return h, domainSvc, r
}

func TestInfrastructureHandler_ListDomains(t *testing.T) {
	t.Run("returns domains from database", func(t *testing.T) {
		_, domainSvc, router := setupInfraDomainHandler()

		// Add domains to mock service
		domainSvc.domains["landing"] = []*domain.ProjectDomain{
			{ID: 1, ProjectID: "landing", Domain: "abc12345.threesix.ai", Type: domain.DomainTypePrimaryAuto, DNSRecordType: "A", Verified: true},
			{ID: 2, ProjectID: "landing", Domain: "landing.threesix.ai", Type: domain.DomainTypePrimaryCustom, DNSRecordType: "A", Verified: true},
		}

		req := httptest.NewRequest("GET", "/projects/landing/domains", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusOK, rec.Body.String())
		}

		var resp map[string]any
		if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
			t.Fatalf("unmarshal: %v", err)
		}
		data := resp["data"].(map[string]any)
		total := int(data["total"].(float64))
		if total != 2 {
			t.Errorf("total = %d, want 2", total)
		}
	})

	t.Run("domain service not configured", func(t *testing.T) {
		h := NewInfrastructureHandler(nil, nil, nil, nil, nil, nil, InfrastructureConfig{
			DefaultGitOwner: "threesix",
			DefaultDomain:   "threesix.ai",
			ClusterIP:       "208.122.204.172",
		})
		r := chi.NewRouter()
		r.Use(testAdminAuth)
		h.Mount(r)

		req := httptest.NewRequest("GET", "/projects/myapp/domains", nil)
		rec := httptest.NewRecorder()
		r.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})
}

func TestInfrastructureHandler_AddDomainAlias(t *testing.T) {
	t.Run("add domain alias", func(t *testing.T) {
		_, domainSvc, router := setupInfraDomainHandler()

		body, _ := json.Marshal(DomainAliasRequest{Domain: "www.threesix.ai"})
		req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusCreated {
			t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String())
		}
		if len(domainSvc.domains["landing"]) != 1 {
			t.Errorf("domains = %d, want 1", len(domainSvc.domains["landing"]))
		}
	})

	t.Run("add primary custom domain", func(t *testing.T) {
		_, domainSvc, router := setupInfraDomainHandler()

		body, _ := json.Marshal(DomainAliasRequest{
			Domain:     "mysite.threesix.ai",
			DomainType: "primary_custom",
		})
		req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusCreated {
			t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusCreated, rec.Body.String())
		}
		if len(domainSvc.domains["landing"]) != 1 {
			t.Errorf("domains = %d, want 1", len(domainSvc.domains["landing"]))
		}
		if domainSvc.domains["landing"][0].Type != domain.DomainTypePrimaryCustom {
			t.Errorf("type = %s, want primary_custom", domainSvc.domains["landing"][0].Type)
		}
	})

	t.Run("invalid type", func(t *testing.T) {
		_, _, router := setupInfraDomainHandler()

		body, _ := json.Marshal(DomainAliasRequest{Domain: "www.threesix.ai", Type: "MX"})
		req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("missing domain", func(t *testing.T) {
		_, _, router := setupInfraDomainHandler()

		body, _ := json.Marshal(DomainAliasRequest{})
		req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("domain service not configured", func(t *testing.T) {
		h := NewInfrastructureHandler(nil, nil, nil, nil, nil, nil, InfrastructureConfig{
			DefaultGitOwner: "threesix",
			DefaultDomain:   "threesix.ai",
			ClusterIP:       "208.122.204.172",
		})
		r := chi.NewRouter()
		r.Use(testAdminAuth)
		h.Mount(r)

		body, _ := json.Marshal(DomainAliasRequest{Domain: "www.threesix.ai"})
		req := httptest.NewRequest("POST", "/projects/landing/domains", bytes.NewReader(body))
		rec := httptest.NewRecorder()
		r.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})
}

func TestInfrastructureHandler_RemoveDomainAlias(t *testing.T) {
	t.Run("removes alias", func(t *testing.T) {
		_, domainSvc, router := setupInfraDomainHandler()
		domainSvc.domains["landing"] = []*domain.ProjectDomain{
			{ID: 1, ProjectID: "landing", Domain: "www.threesix.ai", Type: domain.DomainTypeAlias, DNSRecordType: "A"},
		}

		req := httptest.NewRequest("DELETE", "/projects/landing/domains/www.threesix.ai", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Fatalf("status = %d, want %d; body: %s", rec.Code, http.StatusOK, rec.Body.String())
		}
		if len(domainSvc.domains["landing"]) != 0 {
			t.Errorf("domains = %d, want 0", len(domainSvc.domains["landing"]))
		}
	})

	t.Run("not found", func(t *testing.T) {
		_, _, router := setupInfraDomainHandler()

		req := httptest.NewRequest("DELETE", "/projects/landing/domains/nonexistent.threesix.ai", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusNotFound {
			t.Errorf("status = %d, want %d; body: %s", rec.Code, http.StatusNotFound, rec.Body.String())
		}
	})
}
