package handlers

import (
	"context"
	"errors"
	"net/http"
	"strconv"
	"time"

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

// CheckoutHandler handles checkout/checkin endpoints for sidecar development.
type CheckoutHandler struct {
	checkoutService *service.CheckoutService
}

// NewCheckoutHandler creates a new checkout handler.
func NewCheckoutHandler(checkoutService *service.CheckoutService) *CheckoutHandler {
	return &CheckoutHandler{checkoutService: checkoutService}
}

// Mount registers the checkout routes.
func (h *CheckoutHandler) Mount(r api.Router) {
	r.Route("/projects/{id}/checkout", func(r chi.Router) {
		// Branch listing (read access)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/branches", h.ListBranches)

		// List active checkouts (read access)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/", h.List)

		// Create checkout (execute access - creates tokens)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Post("/", h.Create)

		// Get single checkout (read access)
		r.With(auth.RequireScope(auth.ScopeProjectsRead, auth.ScopeAdmin)).
			Get("/{checkout_id}", h.Get)

		// Checkin (execute access - completes checkout)
		r.With(auth.RequireScope(auth.ScopeProjectsExecute, auth.ScopeAdmin)).
			Post("/{checkout_id}/checkin", h.Checkin)

		// Revoke checkout (admin only - security sensitive)
		r.With(auth.RequireScope(auth.ScopeAdmin)).
			Delete("/{checkout_id}", h.Revoke)
	})
}

// BranchResponse is the JSON response for a branch.
type BranchResponse struct {
	Name      string `json:"name"`
	CommitSHA string `json:"commit_sha"`
	Protected bool   `json:"protected"`
}

// ListBranches returns all branches for a project's repository.
// GET /projects/{id}/checkout/branches
func (h *CheckoutHandler) ListBranches(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, "invalid project id")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutLookup)
	defer cancel()

	branches, err := h.checkoutService.ListBranches(ctx, domain.ProjectID(projectID))
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, "project not found")
			return
		}
		api.WriteInternalError(w, r, "Failed to list branches")
		return
	}

	resp := make([]BranchResponse, len(branches))
	for i, b := range branches {
		resp[i] = BranchResponse{
			Name:      b.Name,
			CommitSHA: b.CommitSHA,
			Protected: b.Protected,
		}
	}

	api.WriteSuccess(w, r, map[string]any{
		"branches": resp,
	})
}

// CheckoutRequest is the JSON body for creating a checkout.
type CheckoutRequest struct {
	Branch      string `json:"branch,omitempty"`       // Existing branch to checkout
	NewBranch   string `json:"new_branch,omitempty"`   // New branch to create
	FromRef     string `json:"from_ref,omitempty"`     // Reference for new branch (default: main)
	FeatureSlug string `json:"feature_slug,omitempty"` // Optional SDLC feature link
	ExpiresIn   string `json:"expires_in,omitempty"`   // Duration string (e.g., "24h", "7d")
}

// CheckoutResponse is the JSON response for a checkout.
type CheckoutResponse struct {
	ID           string  `json:"id"`
	ProjectID    string  `json:"project_id"`
	Branch       string  `json:"branch"`
	FeatureSlug  string  `json:"feature_slug,omitempty"`
	CloneURL     string  `json:"clone_url"`
	CheckedOutBy string  `json:"checked_out_by"`
	CheckedOutAt string  `json:"checked_out_at"`
	ExpiresAt    string  `json:"expires_at"`
	Status       string  `json:"status"`
	CheckedInAt  *string `json:"checked_in_at,omitempty"`
	ReviewTaskID string  `json:"review_task_id,omitempty"`
	Instructions string  `json:"instructions,omitempty"`
}

// List returns active checkouts for a project.
// GET /projects/{id}/checkout
func (h *CheckoutHandler) List(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, "invalid project id")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutFastLookup)
	defer cancel()

	checkouts, err := h.checkoutService.List(ctx, domain.ProjectID(projectID))
	if err != nil {
		api.WriteInternalError(w, r, "Failed to list checkouts")
		return
	}

	resp := make([]CheckoutResponse, len(checkouts))
	for i, c := range checkouts {
		resp[i] = checkoutToResponse(c, "")
	}

	api.WriteSuccess(w, r, map[string]any{
		"checkouts": resp,
	})
}

// Create creates a new checkout with a temporary git token.
// POST /projects/{id}/checkout
func (h *CheckoutHandler) Create(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, "invalid project id")
		return
	}

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

	// Validate: need either branch or new_branch
	if req.Branch == "" && req.NewBranch == "" {
		api.WriteBadRequest(w, r, "branch or new_branch is required")
		return
	}
	if req.Branch != "" && req.NewBranch != "" {
		api.WriteBadRequest(w, r, "specify either branch or new_branch, not both")
		return
	}

	// Validate branch name if provided
	if req.NewBranch != "" {
		if err := validate.Name(req.NewBranch, "new_branch"); err != nil {
			api.WriteBadRequest(w, r, err.Error())
			return
		}
	}

	// Parse expiry duration
	var expiresIn time.Duration
	if req.ExpiresIn != "" {
		var err error
		expiresIn, err = time.ParseDuration(req.ExpiresIn)
		if err != nil {
			// Try parsing as days (e.g., "7d")
			if len(req.ExpiresIn) > 1 && req.ExpiresIn[len(req.ExpiresIn)-1] == 'd' {
				days, parseErr := strconv.Atoi(req.ExpiresIn[:len(req.ExpiresIn)-1])
				if parseErr == nil && days > 0 && days <= 30 {
					expiresIn = time.Duration(days) * 24 * time.Hour
				} else {
					api.WriteBadRequest(w, r, "expires_in must be a valid duration (e.g., 24h, 7d)")
					return
				}
			} else {
				api.WriteBadRequest(w, r, "expires_in must be a valid duration (e.g., 24h, 7d)")
				return
			}
		}
		// Cap at 30 days
		if expiresIn > 30*24*time.Hour {
			api.WriteBadRequest(w, r, "expires_in cannot exceed 30 days")
			return
		}
	}

	// Get user from API key
	checkedOutBy := "unknown"
	if apiKey := auth.GetAPIKey(r.Context()); apiKey != nil {
		checkedOutBy = string(apiKey.ID)
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	result, err := h.checkoutService.Checkout(ctx, service.CheckoutRequest{
		ProjectID:    domain.ProjectID(projectID),
		Branch:       req.Branch,
		NewBranch:    req.NewBranch,
		FromRef:      req.FromRef,
		FeatureSlug:  req.FeatureSlug,
		ExpiresIn:    expiresIn,
		CheckedOutBy: checkedOutBy,
	})
	if err != nil {
		if errors.Is(err, domain.ErrProjectNotFound) {
			api.WriteNotFound(w, r, "project not found")
			return
		}
		if errors.Is(err, domain.ErrBranchNotFound) {
			api.WriteNotFound(w, r, "branch not found")
			return
		}
		if errors.Is(err, domain.ErrBranchProtected) {
			api.WriteBadRequest(w, r, "cannot checkout protected branch")
			return
		}
		if errors.Is(err, domain.ErrCheckoutAlreadyExists) {
			api.WriteError(w, r, http.StatusConflict, "CHECKOUT_EXISTS",
				"active checkout already exists for this branch")
			return
		}
		api.WriteInternalError(w, r, "Failed to create checkout")
		return
	}

	// Return authenticated URL only at creation time
	resp := checkoutToResponse(result.Checkout, result.Instructions)
	resp.CloneURL = result.AuthenticatedCloneURL // Override with authenticated URL for creation response
	api.WriteCreated(w, r, resp)
}

// Get retrieves a checkout by ID.
// GET /projects/{id}/checkout/{checkout_id}
func (h *CheckoutHandler) Get(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, "invalid project id")
		return
	}

	checkoutID := chi.URLParam(r, "checkout_id")
	if checkoutID == "" {
		api.WriteBadRequest(w, r, "checkout_id is required")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutFastLookup)
	defer cancel()

	checkout, err := h.checkoutService.Get(ctx, domain.CheckoutID(checkoutID))
	if err != nil {
		if errors.Is(err, domain.ErrCheckoutNotFound) {
			api.WriteNotFound(w, r, "checkout not found")
			return
		}
		api.WriteInternalError(w, r, "Failed to get checkout")
		return
	}

	// Verify checkout belongs to project
	if string(checkout.ProjectID) != projectID {
		api.WriteNotFound(w, r, "checkout not found")
		return
	}

	api.WriteSuccess(w, r, checkoutToResponse(checkout, ""))
}

// CheckinRequest is the JSON body for checking in.
type CheckinRequestBody struct {
	SkipReview bool `json:"skip_review,omitempty"` // Skip automatic review
	AutoMerge  bool `json:"auto_merge,omitempty"`  // Merge to main if review passes
}

// CheckinResponse is the JSON response for a checkin.
type CheckinResponse struct {
	CheckoutID   string `json:"checkout_id"`
	Status       string `json:"status"`
	ReviewTaskID string `json:"review_task_id,omitempty"`
	Message      string `json:"message"`
}

// Checkin completes a checkout and optionally queues a review.
// POST /projects/{id}/checkout/{checkout_id}/checkin
func (h *CheckoutHandler) Checkin(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, "invalid project id")
		return
	}

	checkoutID := chi.URLParam(r, "checkout_id")
	if checkoutID == "" {
		api.WriteBadRequest(w, r, "checkout_id is required")
		return
	}

	var req CheckinRequestBody
	if err := api.DecodeJSON(r, &req); err != nil {
		// Empty body is OK - use defaults
		req = CheckinRequestBody{}
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// First verify checkout exists and belongs to project
	checkout, err := h.checkoutService.Get(ctx, domain.CheckoutID(checkoutID))
	if err != nil {
		if errors.Is(err, domain.ErrCheckoutNotFound) {
			api.WriteNotFound(w, r, "checkout not found")
			return
		}
		api.WriteInternalError(w, r, "Failed to get checkout")
		return
	}

	if string(checkout.ProjectID) != projectID {
		api.WriteNotFound(w, r, "checkout not found")
		return
	}

	result, err := h.checkoutService.Checkin(ctx, service.CheckinRequest{
		CheckoutID: domain.CheckoutID(checkoutID),
		SkipReview: req.SkipReview,
		AutoMerge:  req.AutoMerge,
	})
	if err != nil {
		if errors.Is(err, domain.ErrCheckoutNotFound) {
			api.WriteNotFound(w, r, "checkout not found")
			return
		}
		if errors.Is(err, domain.ErrCheckoutNotActive) {
			api.WriteBadRequest(w, r, "checkout is not active")
			return
		}
		api.WriteInternalError(w, r, "Failed to checkin")
		return
	}

	message := "Checkout completed. Token has been revoked."
	if result.ReviewTaskID != "" {
		message = "Checkout completed. Review task queued."
	}

	api.WriteSuccess(w, r, CheckinResponse{
		CheckoutID:   string(result.CheckoutID),
		Status:       string(result.Status),
		ReviewTaskID: result.ReviewTaskID,
		Message:      message,
	})
}

// Revoke manually revokes an active checkout.
// DELETE /projects/{id}/checkout/{checkout_id}
func (h *CheckoutHandler) Revoke(w http.ResponseWriter, r *http.Request) {
	projectID := chi.URLParam(r, "id")
	if err := domain.ValidateProjectID(projectID); err != nil {
		api.WriteBadRequest(w, r, "invalid project id")
		return
	}

	checkoutID := chi.URLParam(r, "checkout_id")
	if checkoutID == "" {
		api.WriteBadRequest(w, r, "checkout_id is required")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), TimeoutStandard)
	defer cancel()

	// First verify checkout exists and belongs to project
	checkout, err := h.checkoutService.Get(ctx, domain.CheckoutID(checkoutID))
	if err != nil {
		if errors.Is(err, domain.ErrCheckoutNotFound) {
			api.WriteNotFound(w, r, "checkout not found")
			return
		}
		api.WriteInternalError(w, r, "Failed to get checkout")
		return
	}

	if string(checkout.ProjectID) != projectID {
		api.WriteNotFound(w, r, "checkout not found")
		return
	}

	if err := h.checkoutService.Revoke(ctx, domain.CheckoutID(checkoutID)); err != nil {
		if errors.Is(err, domain.ErrCheckoutNotFound) {
			api.WriteNotFound(w, r, "checkout not found")
			return
		}
		if errors.Is(err, domain.ErrCheckoutNotActive) {
			api.WriteBadRequest(w, r, "checkout is not active")
			return
		}
		api.WriteInternalError(w, r, "Failed to revoke checkout")
		return
	}

	api.WriteSuccess(w, r, map[string]string{
		"status":  "revoked",
		"id":      checkoutID,
		"message": "Checkout revoked. Token has been invalidated.",
	})
}

// checkoutToResponse converts a domain checkout to a response.
func checkoutToResponse(c *domain.Checkout, instructions string) CheckoutResponse {
	resp := CheckoutResponse{
		ID:           string(c.ID),
		ProjectID:    string(c.ProjectID),
		Branch:       c.Branch,
		FeatureSlug:  c.FeatureSlug,
		CloneURL:     c.CloneURL,
		CheckedOutBy: c.CheckedOutBy,
		CheckedOutAt: c.CheckedOutAt.Format(time.RFC3339),
		ExpiresAt:    c.ExpiresAt.Format(time.RFC3339),
		Status:       string(c.Status),
		ReviewTaskID: c.ReviewTaskID,
		Instructions: instructions,
	}

	if c.CheckedInAt != nil {
		s := c.CheckedInAt.Format(time.RFC3339)
		resp.CheckedInAt = &s
	}

	return resp
}
