package handlers

import (
	"context"
	"fmt"
	"time"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// mockGitRepository implements port.GitRepository for testing.
type mockGitRepository struct {
	repos map[string]*domain.Repo
	err   error
}

func newMockGitRepository() *mockGitRepository {
	return &mockGitRepository{repos: make(map[string]*domain.Repo)}
}

func (m *mockGitRepository) CreateRepo(_ context.Context, name, description string, private bool) (*domain.Repo, error) {
	if m.err != nil {
		return nil, m.err
	}
	repo := &domain.Repo{
		ID:          1,
		Owner:       "threesix",
		Name:        name,
		FullName:    "threesix/" + name,
		Description: description,
		Private:     private,
		CloneSSH:    fmt.Sprintf("git@git.threesix.ai:threesix/%s.git", name),
		CloneHTTP:   fmt.Sprintf("https://git.threesix.ai/threesix/%s.git", name),
		HTMLURL:     fmt.Sprintf("https://git.threesix.ai/threesix/%s", name),
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}
	m.repos[name] = repo
	return repo, nil
}

func (m *mockGitRepository) DeleteRepo(_ context.Context, _, name string) error {
	if m.err != nil {
		return m.err
	}
	delete(m.repos, name)
	return nil
}

func (m *mockGitRepository) ListRepos(_ context.Context, _ string) ([]*domain.Repo, error) {
	if m.err != nil {
		return nil, m.err
	}
	var repos []*domain.Repo
	for _, r := range m.repos {
		repos = append(repos, r)
	}
	return repos, nil
}

func (m *mockGitRepository) GetRepo(_ context.Context, _, name string) (*domain.Repo, error) {
	if m.err != nil {
		return nil, m.err
	}
	r, ok := m.repos[name]
	if !ok {
		return nil, fmt.Errorf("repo not found: %s", name)
	}
	return r, nil
}

func (m *mockGitRepository) AddCollaborator(context.Context, string, string, string, string) error {
	return m.err
}
func (m *mockGitRepository) RemoveCollaborator(context.Context, string, string, string) error {
	return m.err
}
func (m *mockGitRepository) AddDeployKey(context.Context, string, string, string, string, bool) (*domain.DeployKey, error) {
	return nil, m.err
}
func (m *mockGitRepository) DeleteDeployKey(context.Context, string, string, int64) error {
	return m.err
}
func (m *mockGitRepository) CreateWebhook(context.Context, string, string, string, string, []string) (*domain.RepoWebhook, error) {
	return nil, m.err
}
func (m *mockGitRepository) DeleteWebhook(context.Context, string, string, int64) error {
	return m.err
}

func (m *mockGitRepository) ListBranches(_ context.Context, _, _ string) ([]*domain.GitBranch, error) {
	if m.err != nil {
		return nil, m.err
	}
	return []*domain.GitBranch{
		{Name: "main", CommitSHA: "abc123", Protected: true},
		{Name: "develop", CommitSHA: "def456", Protected: false},
	}, nil
}

func (m *mockGitRepository) CreateBranch(_ context.Context, _, _, branchName, _ string) (*domain.GitBranch, error) {
	if m.err != nil {
		return nil, m.err
	}
	return &domain.GitBranch{
		Name:      branchName,
		CommitSHA: "newcommit123",
		Protected: false,
	}, nil
}

func (m *mockGitRepository) CreateAccessToken(_ context.Context, name string, _ []string, _ *time.Time) (*domain.GitAccessToken, error) {
	if m.err != nil {
		return nil, m.err
	}
	return &domain.GitAccessToken{
		ID:     12345,
		Name:   name,
		Token:  "test-token-12345",
		Scopes: []string{"write:repository"},
	}, nil
}

func (m *mockGitRepository) DeleteAccessToken(_ context.Context, _ int64) error {
	return m.err
}

// mockDNSProvider implements port.DNSProvider for testing.
type mockDNSProvider struct {
	records map[string]*domain.DNSRecord
	err     error
}

func newMockDNSProvider() *mockDNSProvider {
	return &mockDNSProvider{records: make(map[string]*domain.DNSRecord)}
}

func (m *mockDNSProvider) CreateRecord(_ context.Context, record domain.DNSRecord) (*domain.DNSRecord, error) {
	if m.err != nil {
		return nil, m.err
	}
	record.ID = "rec-" + record.Name
	m.records[record.Name] = &record
	return &record, nil
}

func (m *mockDNSProvider) UpdateRecord(_ context.Context, recordID string, record domain.DNSRecord) (*domain.DNSRecord, error) {
	if m.err != nil {
		return nil, m.err
	}
	record.ID = recordID
	m.records[recordID] = &record
	return &record, nil
}

func (m *mockDNSProvider) DeleteRecord(_ context.Context, recordID string) error {
	if m.err != nil {
		return m.err
	}
	delete(m.records, recordID)
	return nil
}

func (m *mockDNSProvider) DeleteRecordByName(_ context.Context, _, name string) error {
	if m.err != nil {
		return m.err
	}
	delete(m.records, name)
	return nil
}

func (m *mockDNSProvider) GetRecord(_ context.Context, recordID string) (*domain.DNSRecord, error) {
	if m.err != nil {
		return nil, m.err
	}
	r, ok := m.records[recordID]
	if !ok {
		return nil, fmt.Errorf("record not found")
	}
	return r, nil
}

func (m *mockDNSProvider) ListRecords(_ context.Context, recordType string) ([]*domain.DNSRecord, error) {
	if m.err != nil {
		return nil, m.err
	}
	var result []*domain.DNSRecord
	for _, r := range m.records {
		if recordType == "" || r.Type == recordType {
			result = append(result, r)
		}
	}
	return result, nil
}

func (m *mockDNSProvider) FindRecord(_ context.Context, _, name string) (*domain.DNSRecord, error) {
	if m.err != nil {
		return nil, m.err
	}
	r, ok := m.records[name]
	if !ok {
		return nil, nil
	}
	return r, nil
}

func (m *mockDNSProvider) UpsertRecord(ctx context.Context, record domain.DNSRecord) (*domain.DNSRecord, error) {
	if m.err != nil {
		return nil, m.err
	}
	// Check if record exists, then update or create
	existing, _ := m.FindRecord(ctx, record.Type, record.Name)
	if existing != nil {
		return m.UpdateRecord(ctx, existing.ID, record)
	}
	return m.CreateRecord(ctx, record)
}

// mockDeployer implements port.Deployer for testing.
type mockDeployer struct {
	deployments map[string]*domain.DeployStatus
	logs        string
	err         error
}

func newMockDeployer() *mockDeployer {
	return &mockDeployer{deployments: make(map[string]*domain.DeployStatus)}
}

func (m *mockDeployer) Deploy(_ context.Context, spec domain.DeploySpec) error {
	if m.err != nil {
		return m.err
	}
	m.deployments[spec.ProjectName] = &domain.DeployStatus{
		ProjectName:   spec.ProjectName,
		Image:         spec.Image,
		Replicas:      spec.Replicas,
		ReadyReplicas: 0,
		URL:           "https://" + spec.Domain,
		Status:        domain.DeploymentStatusPending,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}
	return nil
}

func (m *mockDeployer) Undeploy(_ context.Context, projectName string) error {
	if m.err != nil {
		return m.err
	}
	delete(m.deployments, projectName)
	return nil
}

func (m *mockDeployer) UndeployAll(_ context.Context, projectName string) error {
	if m.err != nil {
		return m.err
	}
	delete(m.deployments, projectName)
	return nil
}

func (m *mockDeployer) GetStatus(_ context.Context, projectName string) (*domain.DeployStatus, error) {
	if m.err != nil {
		return nil, m.err
	}
	s, ok := m.deployments[projectName]
	if !ok {
		return nil, nil
	}
	return s, nil
}

func (m *mockDeployer) Restart(_ context.Context, _ string) error {
	return m.err
}

func (m *mockDeployer) Scale(_ context.Context, projectName string, replicas int) error {
	if m.err != nil {
		return m.err
	}
	if s, ok := m.deployments[projectName]; ok {
		s.Replicas = replicas
	}
	return nil
}

func (m *mockDeployer) GetLogs(_ context.Context, _ string, _ int) (string, error) {
	if m.err != nil {
		return "", m.err
	}
	return m.logs, nil
}

func (m *mockDeployer) AddIngressHost(_ context.Context, _, _ string) error {
	return m.err
}

func (m *mockDeployer) RemoveIngressHost(_ context.Context, _, _ string) error {
	return m.err
}

func (m *mockDeployer) UndeployComponent(_ context.Context, _, _ string) error {
	return m.err
}

func (m *mockDeployer) GetComponentStatus(_ context.Context, _, _ string) (*domain.DeployStatus, error) {
	if m.err != nil {
		return nil, m.err
	}
	return nil, nil
}

func (m *mockDeployer) ListComponentStatuses(_ context.Context, _ string) (*domain.ProjectDeployStatus, error) {
	if m.err != nil {
		return nil, m.err
	}
	return &domain.ProjectDeployStatus{}, nil
}

func (m *mockDeployer) RestartComponent(_ context.Context, _, _ string) error {
	return m.err
}

func (m *mockDeployer) ScaleComponent(_ context.Context, _, _ string, _ int) error {
	return m.err
}

func (m *mockDeployer) GetComponentLogs(_ context.Context, _, _ string, _ int) (string, error) {
	if m.err != nil {
		return "", m.err
	}
	return m.logs, nil
}

func (m *mockDeployer) AddIngressPath(_ context.Context, _, _, _, _ string, _ int) error {
	return m.err
}

func (m *mockDeployer) RemoveIngressPath(_ context.Context, _, _, _ string) error {
	return m.err
}

// mockPreviewManager implements port.PreviewManager for testing.
type mockPreviewManager struct {
	previews map[string]bool
	err      error
}

func newMockPreviewManager() *mockPreviewManager {
	return &mockPreviewManager{previews: make(map[string]bool)}
}

func (m *mockPreviewManager) CreatePreview(_ context.Context, opts port.PreviewOptions) error {
	if m.err != nil {
		return m.err
	}
	m.previews[opts.SessionID] = true
	return nil
}

func (m *mockPreviewManager) DeletePreview(_ context.Context, sessionID string) error {
	if m.err != nil {
		return m.err
	}
	delete(m.previews, sessionID)
	return nil
}
