// Package handlers provides HTTP handlers for the rdev API.
package handlers

import (
	"context"
	"errors"
	"fmt"
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/auth"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
	"github.com/orchard9/rdev/pkg/api"
)

// DomainAddRequest contains parameters for adding a domain.
type DomainAddRequest struct {
	ProjectID  string
	Domain     string
	Type       domain.DomainType
	RecordType string
	Proxied    bool
}

// DomainService defines the interface for domain management operations.
// This interface is implemented by service.ProjectInfraService.
type DomainService interface {
	ListDomains(ctx context.Context, projectID string) ([]*domain.ProjectDomain, error)
	AddDomain(ctx context.Context, req DomainAddRequest) (*domain.ProjectDomain, error)
	RemoveDomain(ctx context.Context, projectID, fqdn string) error
}

// InfrastructureHandler handles git, deployment, DNS, and CI pipeline endpoints.
type InfrastructureHandler struct {
	gitRepo       port.GitRepository
	dns           port.DNSProvider
	deployer      port.Deployer
	projects      port.ProjectRepository
	ciProvider    port.CIProvider
	domainService DomainService

	// Config
	defaultGitOwner string
	defaultDomain   string
	clusterIP       string
}

// validateProjectID validates that a project ID is safe for use as repo/deployment name.
func validateProjectID(id string) error {
	return domain.ValidateProjectID(id)
}

// InfrastructureConfig configures the infrastructure handler.
type InfrastructureConfig struct {
	// DefaultGitOwner is the default org/user for new repos (e.g., "threesix")
	DefaultGitOwner string
	// DefaultDomain is the base domain for auto-generated URLs (e.g., "threesix.ai")
	DefaultDomain string
	// ClusterIP is the external IP address for DNS records (e.g., "208.122.204.172")
	ClusterIP string
}

// NewInfrastructureHandler creates a new infrastructure handler.
func NewInfrastructureHandler(
	gitRepo port.GitRepository,
	dns port.DNSProvider,
	deployer port.Deployer,
	projects port.ProjectRepository,
	ciProvider port.CIProvider,
	domainService DomainService,
	cfg InfrastructureConfig,
) *InfrastructureHandler {
	return &InfrastructureHandler{
		gitRepo:         gitRepo,
		dns:             dns,
		deployer:        deployer,
		projects:        projects,
		ciProvider:      ciProvider,
		domainService:   domainService,
		defaultGitOwner: cfg.DefaultGitOwner,
		defaultDomain:   cfg.DefaultDomain,
		clusterIP:       cfg.ClusterIP,
	}
}

// Mount registers the infrastructure routes.
func (h *InfrastructureHandler) Mount(r api.Router) {
	// Git repository endpoints
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Post("/projects/{id}/repo", h.CreateRepo)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/projects/{id}/repo", h.GetRepo)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Delete("/projects/{id}/repo", h.DeleteRepo)

	// Deployment endpoints
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Post("/projects/{id}/deploy", h.Deploy)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/projects/{id}/deploy/status", h.GetDeployStatus)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Delete("/projects/{id}/deploy", h.Undeploy)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Post("/projects/{id}/deploy/restart", h.RestartDeploy)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Post("/projects/{id}/deploy/scale", h.ScaleDeploy)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/projects/{id}/deploy/logs", h.GetDeployLogs)

	// Domain endpoints (single)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Post("/projects/{id}/domain", h.AddDomain)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Delete("/projects/{id}/domain", h.RemoveDomain)

	// Domain alias management (multi-domain)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/projects/{id}/domains", h.ListDomains)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Post("/projects/{id}/domains", h.AddDomainAlias)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Delete("/projects/{id}/domains/{domain}", h.RemoveDomainAlias)

	// CI pipeline endpoints
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/projects/{id}/pipelines", h.ListPipelines)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/projects/{id}/pipelines/{number}", h.GetPipeline)
	r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
		Get("/projects/{id}/pipelines/{number}/steps", h.GetPipelineSteps)
	r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
		Post("/projects/{id}/pipelines/{number}/retry", h.RetryPipeline)
}

// CreateRepoRequest is the request body for POST /projects/{id}/repo.
type CreateRepoRequest struct {
	Description string `json:"description,omitempty"`
	Private     bool   `json:"private,omitempty"`
}

// CreateRepoResponse is the response for POST /projects/{id}/repo.
type CreateRepoResponse struct {
	ID          int64  `json:"id"`
	Owner       string `json:"owner"`
	Name        string `json:"name"`
	FullName    string `json:"full_name"`
	Description string `json:"description,omitempty"`
	Private     bool   `json:"private"`
	CloneSSH    string `json:"clone_ssh"`
	CloneHTTP   string `json:"clone_http"`
	HTMLURL     string `json:"html_url"`
}

// CreateRepo creates a git repository for a project.
// POST /projects/{id}/repo
func (h *InfrastructureHandler) CreateRepo(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// Validate project ID
	if err := validateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.gitRepo == nil {
		api.WriteInternalError(w, r, "git repository not configured")
		return
	}

	var req CreateRepoRequest
	if err := api.DecodeJSON(r, &req); err != nil && !errors.Is(err, api.ErrEmptyBody) {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	// Create the repo
	repo, err := h.gitRepo.CreateRepo(ctx, projectID, req.Description, req.Private)
	if err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to create repo: %v", err))
		return
	}

	// Note: Could update project with repo info here if we had a project repository
	// For now, just return the repo info directly
	_ = h.projects // Silence unused warning if present

	api.WriteCreated(w, r, CreateRepoResponse{
		ID:          repo.ID,
		Owner:       repo.Owner,
		Name:        repo.Name,
		FullName:    repo.FullName,
		Description: repo.Description,
		Private:     repo.Private,
		CloneSSH:    repo.CloneSSH,
		CloneHTTP:   repo.CloneHTTP,
		HTMLURL:     repo.HTMLURL,
	})
}

// GetRepo returns the git repository for a project.
// GET /projects/{id}/repo
func (h *InfrastructureHandler) GetRepo(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

	// Validate project ID
	if err := validateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.gitRepo == nil {
		api.WriteInternalError(w, r, "git repository not configured")
		return
	}

	repo, err := h.gitRepo.GetRepo(ctx, h.defaultGitOwner, projectID)
	if err != nil {
		api.WriteNotFound(w, r, fmt.Sprintf("repo not found: %s/%s", h.defaultGitOwner, projectID))
		return
	}

	api.WriteSuccess(w, r, CreateRepoResponse{
		ID:          repo.ID,
		Owner:       repo.Owner,
		Name:        repo.Name,
		FullName:    repo.FullName,
		Description: repo.Description,
		Private:     repo.Private,
		CloneSSH:    repo.CloneSSH,
		CloneHTTP:   repo.CloneHTTP,
		HTMLURL:     repo.HTMLURL,
	})
}

// DeleteRepo deletes the git repository for a project.
// DELETE /projects/{id}/repo
func (h *InfrastructureHandler) DeleteRepo(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// Validate project ID
	if err := validateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	if h.gitRepo == nil {
		api.WriteInternalError(w, r, "git repository not configured")
		return
	}

	err := h.gitRepo.DeleteRepo(ctx, h.defaultGitOwner, projectID)
	if err != nil {
		api.WriteInternalError(w, r, fmt.Sprintf("failed to delete repo: %v", err))
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"status":  "deleted",
		"project": projectID,
	})
}

// AddDomainRequest is the request body for POST /projects/{id}/domain.
type AddDomainRequest struct {
	Domain string `json:"domain"` // Custom domain (e.g., "myapp.example.com")
}

// AddDomain adds a custom domain to a project.
// POST /projects/{id}/domain
func (h *InfrastructureHandler) AddDomain(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// Validate project ID
	if err := validateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	var req AddDomainRequest
	if err := api.DecodeJSON(r, &req); err != nil {
		api.WriteBadRequest(w, r, "invalid request body")
		return
	}

	if req.Domain == "" {
		api.WriteBadRequest(w, r, "domain is required")
		return
	}

	// Create DNS record if it's a threesix.ai subdomain
	if h.dns != nil && h.clusterIP != "" && isSubdomain(req.Domain, h.defaultDomain) {
		subdomain := getSubdomain(req.Domain, h.defaultDomain)
		_, err := h.dns.CreateRecord(ctx, domain.DNSRecord{
			Type:    "A",
			Name:    subdomain,
			Content: h.clusterIP,
			TTL:     1,
			Proxied: false,
		})
		if err != nil {
			api.WriteInternalError(w, r, fmt.Sprintf("failed to create DNS record: %v", err))
			return
		}
	}

	// TODO: Update ingress with new domain
	// This would require getting the current deployment and updating it

	note := "Domain configured"
	if !isSubdomain(req.Domain, h.defaultDomain) && h.clusterIP != "" {
		note = fmt.Sprintf("External domain configured. Point your DNS to %s", h.clusterIP)
	}

	api.WriteCreated(w, r, map[string]string{
		"project": projectID,
		"domain":  req.Domain,
		"status":  "configured",
		"note":    note,
	})
}

// RemoveDomain removes a custom domain from a project.
// DELETE /projects/{id}/domain
func (h *InfrastructureHandler) RemoveDomain(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// Validate project ID
	if err := validateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, err.Error())
		return
	}

	domainName := r.URL.Query().Get("domain")
	if domainName == "" {
		api.WriteBadRequest(w, r, "domain query parameter is required")
		return
	}

	// Delete DNS record if it's a threesix.ai subdomain
	if h.dns != nil && isSubdomain(domainName, h.defaultDomain) {
		subdomain := getSubdomain(domainName, h.defaultDomain)
		_ = h.dns.DeleteRecordByName(ctx, "A", subdomain)
	}

	// TODO: Update ingress to remove the domain

	api.WriteSuccess(w, r, map[string]string{
		"project": projectID,
		"domain":  domainName,
		"status":  "removed",
	})
}

// Helper functions

func isSubdomain(domain, baseDomain string) bool {
	suffix := "." + baseDomain
	return len(domain) > len(suffix) && domain[len(domain)-len(suffix):] == suffix
}

func getSubdomain(domain, baseDomain string) string {
	suffix := "." + baseDomain
	if len(domain) > len(suffix) && domain[len(domain)-len(suffix):] == suffix {
		return domain[:len(domain)-len(suffix)]
	}
	return domain
}
