// Package gitea provides a Gitea API adapter implementing port.GitRepository.
//
// Context Propagation Note:
// The Gitea Go SDK (code.gitea.io/sdk/gitea) does not natively support context
// propagation for HTTP requests. Methods accept context.Context for interface
// compatibility and future-proofing, but the underlying SDK calls do not use it
// for cancellation or timeouts. If cancellation is critical, consider using a
// context-aware HTTP transport or wrapping calls with context deadline checks.
package gitea

import (
	"context"
	"fmt"
	"time"

	"code.gitea.io/sdk/gitea"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// Ensure Client implements GitRepository and ExternalHealthChecker.
var _ port.GitRepository = (*Client)(nil)
var _ port.ExternalHealthChecker = (*Client)(nil)

// Client is a Gitea API client adapter.
type Client struct {
	client       *gitea.Client
	url          string // Gitea server URL for health checks
	defaultOwner string // default organization/user for new repos
}

// SDKClient returns the underlying Gitea SDK client.
// Used by the template provider to create files in repos.
func (c *Client) SDKClient() *gitea.Client {
	return c.client
}

// NewClient creates a new Gitea client.
// url is the Gitea server URL (e.g., https://git.threesix.ai)
// token is an API access token with repo permissions
// defaultOwner is the organization or user to create repos under
func NewClient(url, token, defaultOwner string) (*Client, error) {
	client, err := gitea.NewClient(url, gitea.SetToken(token))
	if err != nil {
		return nil, fmt.Errorf("failed to create gitea client: %w", err)
	}
	return &Client{
		client:       client,
		url:          url,
		defaultOwner: defaultOwner,
	}, nil
}

// CreateRepo creates a new git repository under the default owner.
func (c *Client) CreateRepo(ctx context.Context, name, description string, private bool) (*domain.Repo, error) {
	opts := gitea.CreateRepoOption{
		Name:          name,
		Description:   description,
		Private:       private,
		AutoInit:      false, // Empty repo - template seeding will create all files
		DefaultBranch: "main",
	}

	var repo *gitea.Repository
	var err error

	// Try to create as org repo first, fall back to user repo
	repo, _, err = c.client.CreateOrgRepo(c.defaultOwner, opts)
	if err != nil {
		// May not be an org, try as user repo
		repo, _, err = c.client.CreateRepo(opts)
		if err != nil {
			return nil, fmt.Errorf("failed to create repo: %w", err)
		}
	}

	return repoFromGitea(repo), nil
}

// DeleteRepo deletes a repository.
func (c *Client) DeleteRepo(ctx context.Context, owner, name string) error {
	_, err := c.client.DeleteRepo(owner, name)
	if err != nil {
		return fmt.Errorf("failed to delete repo %s/%s: %w", owner, name, err)
	}
	return nil
}

// ListRepos returns all repositories for an owner.
func (c *Client) ListRepos(ctx context.Context, owner string) ([]*domain.Repo, error) {
	// Try as organization first
	repos, _, err := c.client.ListOrgRepos(owner, gitea.ListOrgReposOptions{
		ListOptions: gitea.ListOptions{PageSize: 100},
	})
	if err != nil {
		// Try as user
		repos, _, err = c.client.ListUserRepos(owner, gitea.ListReposOptions{
			ListOptions: gitea.ListOptions{PageSize: 100},
		})
		if err != nil {
			return nil, fmt.Errorf("failed to list repos for %s: %w", owner, err)
		}
	}

	result := make([]*domain.Repo, len(repos))
	for i, r := range repos {
		result[i] = repoFromGitea(r)
	}
	return result, nil
}

// GetRepo returns a single repository.
func (c *Client) GetRepo(ctx context.Context, owner, name string) (*domain.Repo, error) {
	repo, _, err := c.client.GetRepo(owner, name)
	if err != nil {
		return nil, fmt.Errorf("failed to get repo %s/%s: %w", owner, name, err)
	}
	return repoFromGitea(repo), nil
}

// AddCollaborator adds a user as collaborator to a repo.
func (c *Client) AddCollaborator(ctx context.Context, owner, repo, username string, permission string) error {
	var accessMode gitea.AccessMode
	switch permission {
	case "read":
		accessMode = gitea.AccessModeRead
	case "write":
		accessMode = gitea.AccessModeWrite
	case "admin":
		accessMode = gitea.AccessModeAdmin
	default:
		accessMode = gitea.AccessModeRead
	}

	_, err := c.client.AddCollaborator(owner, repo, username, gitea.AddCollaboratorOption{
		Permission: &accessMode,
	})
	if err != nil {
		return fmt.Errorf("failed to add collaborator %s to %s/%s: %w", username, owner, repo, err)
	}
	return nil
}

// RemoveCollaborator removes a collaborator from a repo.
func (c *Client) RemoveCollaborator(ctx context.Context, owner, repo, username string) error {
	_, err := c.client.DeleteCollaborator(owner, repo, username)
	if err != nil {
		return fmt.Errorf("failed to remove collaborator %s from %s/%s: %w", username, owner, repo, err)
	}
	return nil
}

// AddDeployKey adds a deploy key to a repo.
func (c *Client) AddDeployKey(ctx context.Context, owner, repo, title, publicKey string, readOnly bool) (*domain.DeployKey, error) {
	key, _, err := c.client.CreateDeployKey(owner, repo, gitea.CreateKeyOption{
		Title:    title,
		Key:      publicKey,
		ReadOnly: readOnly,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to add deploy key to %s/%s: %w", owner, repo, err)
	}
	return &domain.DeployKey{
		ID:        key.ID,
		Title:     key.Title,
		PublicKey: key.Key,
		ReadOnly:  key.ReadOnly,
		CreatedAt: key.Created,
	}, nil
}

// DeleteDeployKey removes a deploy key from a repo.
func (c *Client) DeleteDeployKey(ctx context.Context, owner, repo string, keyID int64) error {
	_, err := c.client.DeleteDeployKey(owner, repo, keyID)
	if err != nil {
		return fmt.Errorf("failed to delete deploy key %d from %s/%s: %w", keyID, owner, repo, err)
	}
	return nil
}

// CreateWebhook creates a webhook on a repository.
func (c *Client) CreateWebhook(ctx context.Context, owner, repo, url, secret string, events []string) (*domain.RepoWebhook, error) {
	hook, _, err := c.client.CreateRepoHook(owner, repo, gitea.CreateHookOption{
		Type: gitea.HookTypeGitea,
		Config: map[string]string{
			"url":          url,
			"content_type": "json",
			"secret":       secret,
		},
		Events: events,
		Active: true,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create webhook on %s/%s: %w", owner, repo, err)
	}
	return &domain.RepoWebhook{
		ID:       hook.ID,
		URL:      hook.Config["url"],
		Secret:   secret,
		Events:   hook.Events,
		Active:   hook.Active,
		HookType: string(hook.Type),
	}, nil
}

// DeleteWebhook removes a webhook from a repo.
func (c *Client) DeleteWebhook(ctx context.Context, owner, repo string, webhookID int64) error {
	_, err := c.client.DeleteRepoHook(owner, repo, webhookID)
	if err != nil {
		return fmt.Errorf("failed to delete webhook %d from %s/%s: %w", webhookID, owner, repo, err)
	}
	return nil
}

// Check returns the health status of the Gitea server.
// Implements port.ExternalHealthChecker.
func (c *Client) Check(ctx context.Context) domain.ExternalSystemStatus {
	start := time.Now()
	status := domain.ExternalSystemStatus{
		System: domain.ExternalSystemGit,
		URL:    c.url,
	}

	// Gitea SDK doesn't support context propagation for HTTP requests,
	// but check for cancellation before making the call.
	select {
	case <-ctx.Done():
		status.Latency = time.Since(start)
		status.LastChecked = time.Now().UTC()
		status.Healthy = false
		status.Error = ctx.Err().Error()
		return status
	default:
	}

	// Call ListMyOrgs (lightweight, tests auth)
	_, _, err := c.client.ListMyOrgs(gitea.ListOrgsOptions{
		ListOptions: gitea.ListOptions{PageSize: 1},
	})
	status.Latency = time.Since(start)
	status.LastChecked = time.Now().UTC()

	if err != nil {
		status.Healthy = false
		status.Error = err.Error()
	} else {
		status.Healthy = true
		status.LastHealthy = status.LastChecked
	}

	return status
}

// ListBranches returns all branches for a repository.
func (c *Client) ListBranches(ctx context.Context, owner, repo string) ([]*domain.GitBranch, error) {
	// Gitea SDK doesn't support context propagation, but check for cancellation.
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	branches, _, err := c.client.ListRepoBranches(owner, repo, gitea.ListRepoBranchesOptions{
		ListOptions: gitea.ListOptions{PageSize: 100},
	})
	if err != nil {
		return nil, fmt.Errorf("failed to list branches for %s/%s: %w", owner, repo, err)
	}

	result := make([]*domain.GitBranch, len(branches))
	for i, b := range branches {
		result[i] = &domain.GitBranch{
			Name:      b.Name,
			CommitSHA: b.Commit.ID,
			Protected: b.Protected,
		}
	}
	return result, nil
}

// CreateBranch creates a new branch from a reference (branch name or commit SHA).
func (c *Client) CreateBranch(ctx context.Context, owner, repo, branchName, fromRef string) (*domain.GitBranch, error) {
	// Gitea SDK doesn't support context propagation, but check for cancellation.
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	branch, _, err := c.client.CreateBranch(owner, repo, gitea.CreateBranchOption{
		BranchName:    branchName,
		OldBranchName: fromRef,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create branch %s from %s in %s/%s: %w", branchName, fromRef, owner, repo, err)
	}

	return &domain.GitBranch{
		Name:      branch.Name,
		CommitSHA: branch.Commit.ID,
		Protected: branch.Protected,
	}, nil
}

// CreateAccessToken creates a new personal access token for git operations.
func (c *Client) CreateAccessToken(ctx context.Context, name string, scopes []string, expiresAt *time.Time) (*domain.GitAccessToken, error) {
	// Gitea SDK doesn't support context propagation, but check for cancellation.
	select {
	case <-ctx.Done():
		return nil, ctx.Err()
	default:
	}

	// Convert string scopes to Gitea AccessTokenScope
	tokenScopes := make([]gitea.AccessTokenScope, len(scopes))
	for i, s := range scopes {
		tokenScopes[i] = gitea.AccessTokenScope(s)
	}

	token, _, err := c.client.CreateAccessToken(gitea.CreateAccessTokenOption{
		Name:   name,
		Scopes: tokenScopes,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to create access token: %w", err)
	}

	return &domain.GitAccessToken{
		ID:     token.ID,
		Name:   token.Name,
		Token:  token.Token,
		Scopes: scopes,
	}, nil
}

// DeleteAccessToken revokes and deletes an access token.
func (c *Client) DeleteAccessToken(ctx context.Context, tokenID int64) error {
	// Gitea SDK doesn't support context propagation, but check for cancellation.
	select {
	case <-ctx.Done():
		return ctx.Err()
	default:
	}

	_, err := c.client.DeleteAccessToken(tokenID)
	if err != nil {
		return fmt.Errorf("failed to delete access token %d: %w", tokenID, err)
	}
	return nil
}

// DefaultOwner returns the default organization for repo operations.
func (c *Client) DefaultOwner() string {
	return c.defaultOwner
}

// URL returns the Gitea server URL.
func (c *Client) URL() string {
	return c.url
}

// repoFromGitea converts a gitea.Repository to domain.Repo.
func repoFromGitea(r *gitea.Repository) *domain.Repo {
	return &domain.Repo{
		ID:          r.ID,
		Owner:       r.Owner.UserName,
		Name:        r.Name,
		FullName:    r.FullName,
		Description: r.Description,
		Private:     r.Private,
		CloneSSH:    r.SSHURL,
		CloneHTTP:   r.CloneURL,
		HTMLURL:     r.HTMLURL,
		CreatedAt:   r.Created,
		UpdatedAt:   r.Updated,
	}
}
