package handlers

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"net/http"
	"strconv"

	"golang.org/x/crypto/ssh"

	"chat-switchboard/crypto"
	"chat-switchboard/models"
	"chat-switchboard/store"
	"chat-switchboard/workspace"

	"github.com/gin-gonic/gin"
)

// =========================================
// GIT HANDLER — Git operations on workspaces
// =========================================

type GitHandler struct {
	stores store.Stores
	gitOps *workspace.GitOps
}

func NewGitHandler(s store.Stores, gitOps *workspace.GitOps) *GitHandler {
	return &GitHandler{stores: s, gitOps: gitOps}
}

// ── Clone ────────────────────────────────────

func (h *GitHandler) Clone(c *gin.Context) {
	wsID := c.Param("id")
	var req struct {
		URL          string `json:"url" binding:"required"`
		Branch       string `json:"branch"`
		CredentialID string `json:"credential_id"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	w, err := h.stores.Workspaces.GetByID(c.Request.Context(), wsID)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "workspace not found"})
		return
	}

	if err := h.gitOps.Clone(c.Request.Context(), w, req.URL, req.Branch, req.CredentialID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"status": "cloned"})
}

// ── Pull ─────────────────────────────────────

func (h *GitHandler) Pull(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	if err := h.gitOps.Pull(c.Request.Context(), w); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"status": "pulled"})
}

// ── Push ─────────────────────────────────────

func (h *GitHandler) Push(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	if err := h.gitOps.Push(c.Request.Context(), w); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"status": "pushed"})
}

// ── Status ───────────────────────────────────

func (h *GitHandler) Status(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	status, err := h.gitOps.Status(c.Request.Context(), w)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, status)
}

// ── Diff ─────────────────────────────────────

func (h *GitHandler) Diff(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	path := c.Query("path")
	diff, err := h.gitOps.Diff(c.Request.Context(), w, path)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"diff": diff})
}

// ── Commit ───────────────────────────────────

func (h *GitHandler) Commit(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	var req struct {
		Message string   `json:"message" binding:"required"`
		Paths   []string `json:"paths"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err := h.gitOps.Commit(c.Request.Context(), w, req.Message, req.Paths); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"status": "committed"})
}

// ── Log ──────────────────────────────────────

func (h *GitHandler) Log(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	n := 20
	if v := c.Query("n"); v != "" {
		if parsed, err := strconv.Atoi(v); err == nil && parsed > 0 {
			n = parsed
			if n > 100 {
				n = 100
			}
		}
	}
	entries, err := h.gitOps.Log(c.Request.Context(), w, n)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	if entries == nil {
		entries = []models.GitLogEntry{}
	}
	c.JSON(http.StatusOK, gin.H{"data": entries})
}

// ── Branches ─────────────────────────────────

func (h *GitHandler) Branches(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	branches, current, err := h.gitOps.BranchList(c.Request.Context(), w)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"current":  current,
		"branches": branches,
	})
}

// ── Checkout ─────────────────────────────────

func (h *GitHandler) Checkout(c *gin.Context) {
	w, err := h.loadWorkspace(c)
	if err != nil {
		return
	}
	var req struct {
		Branch string `json:"branch" binding:"required"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if err := h.gitOps.Checkout(c.Request.Context(), w, req.Branch); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	c.JSON(http.StatusOK, gin.H{"status": "checked out", "branch": req.Branch})
}

// loadWorkspace loads the workspace and validates git is configured.
func (h *GitHandler) loadWorkspace(c *gin.Context) (*models.Workspace, error) {
	wsID := c.Param("id")
	w, err := h.stores.Workspaces.GetByID(c.Request.Context(), wsID)
	if err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "workspace not found"})
		return nil, err
	}
	if w.GitRemoteURL == nil || *w.GitRemoteURL == "" {
		err := fmt.Errorf("workspace has no git remote configured")
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return nil, err
	}
	return w, nil
}

// =========================================
// GIT CREDENTIAL HANDLER — CRUD for encrypted git credentials
// =========================================

type GitCredentialHandler struct {
	stores store.Stores
	vault  *crypto.KeyResolver
}

func NewGitCredentialHandler(s store.Stores, vault *crypto.KeyResolver) *GitCredentialHandler {
	return &GitCredentialHandler{stores: s, vault: vault}
}

// Create stores a new encrypted git credential.
func (h *GitCredentialHandler) Create(c *gin.Context) {
	userID := getUserID(c)
	var req struct {
		Name     string `json:"name" binding:"required"`
		AuthType string `json:"auth_type" binding:"required"`
		// Raw credential data — encrypted before storage
		Token      string `json:"token,omitempty"`      // for https_pat
		Username   string `json:"username,omitempty"`    // for https_basic
		Password   string `json:"password,omitempty"`    // for https_basic
		PrivateKey string `json:"private_key,omitempty"` // for ssh_key
		Passphrase string `json:"passphrase,omitempty"`  // for ssh_key
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Validate auth type
	switch req.AuthType {
	case "https_pat":
		if req.Token == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "token is required for https_pat"})
			return
		}
	case "https_basic":
		if req.Username == "" || req.Password == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "username and password required for https_basic"})
			return
		}
	case "ssh_key":
		if req.PrivateKey == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "private_key is required for ssh_key"})
			return
		}
	default:
		c.JSON(http.StatusBadRequest, gin.H{"error": "auth_type must be https_pat, https_basic, or ssh_key"})
		return
	}

	// Build plaintext JSON
	var credData map[string]string
	switch req.AuthType {
	case "https_pat":
		credData = map[string]string{"token": req.Token}
	case "https_basic":
		credData = map[string]string{"username": req.Username, "password": req.Password}
	case "ssh_key":
		credData = map[string]string{"private_key": req.PrivateKey, "passphrase": req.Passphrase}
	}
	plaintext, _ := json.Marshal(credData)

	// Encrypt using global scope (same vault pattern as BYOK)
	ciphertext, nonce, err := h.vault.EncryptForScope(string(plaintext), "global", userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "encryption failed"})
		return
	}

	cred := &models.GitCredential{
		UserID:        userID,
		Name:          req.Name,
		AuthType:      req.AuthType,
		EncryptedData: ciphertext,
		Nonce:         nonce,
	}

	if err := h.stores.GitCredentials.Create(c.Request.Context(), cred); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, cred.Summary())
}

// List returns all git credentials for the current user (summaries only).
func (h *GitCredentialHandler) List(c *gin.Context) {
	userID := getUserID(c)
	creds, err := h.stores.GitCredentials.ListByUser(c.Request.Context(), userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	summaries := make([]models.GitCredentialSummary, 0, len(creds))
	for _, cred := range creds {
		summaries = append(summaries, cred.Summary())
	}
	c.JSON(http.StatusOK, gin.H{"data": summaries})
}

// Delete removes a git credential.
func (h *GitCredentialHandler) Delete(c *gin.Context) {
	userID := getUserID(c)
	credID := c.Param("id")

	if err := h.stores.GitCredentials.Delete(c.Request.Context(), credID, userID); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "credential not found"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"deleted": true})
}

// Generate creates a new ED25519 SSH keypair server-side.
// The private key is vault-encrypted before storage. Only the public key
// and fingerprint are returned — the private key never leaves the server.
//
// POST /git-credentials/generate
func (h *GitCredentialHandler) Generate(c *gin.Context) {
	userID := getUserID(c)
	var req struct {
		Name      string  `json:"name" binding:"required"`
		PersonaID *string `json:"persona_id,omitempty"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Generate ED25519 keypair
	pubKey, privKey, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "key generation failed"})
		return
	}

	// Marshal public key to authorized_keys format
	sshPub, err := ssh.NewPublicKey(pubKey)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "public key encoding failed"})
		return
	}
	authorizedKey := string(ssh.MarshalAuthorizedKey(sshPub))

	// Fingerprint (SHA256)
	fingerprint := ssh.FingerprintSHA256(sshPub)

	// Marshal private key to OpenSSH PEM format
	privPEM, err := ssh.MarshalPrivateKey(privKey, "")
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "private key encoding failed"})
		return
	}
	privPEMBytes := pem.EncodeToMemory(privPEM)

	// Encrypt private key via vault
	credData := map[string]string{"private_key": string(privPEMBytes)}
	plaintext, _ := json.Marshal(credData)

	ciphertext, nonce, err := h.vault.EncryptForScope(string(plaintext), "global", userID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "encryption failed"})
		return
	}

	cred := &models.GitCredential{
		UserID:        userID,
		Name:          req.Name,
		AuthType:      "ssh_key",
		EncryptedData: ciphertext,
		Nonce:         nonce,
		PublicKey:     authorizedKey,
		Fingerprint:   fingerprint,
		PersonaID:     req.PersonaID,
	}

	if err := h.stores.GitCredentials.Create(c.Request.Context(), cred); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusCreated, cred.Summary())
}

// GetPublicKey returns the public key for a credential (for copy-to-clipboard).
//
// GET /git-credentials/:id/public-key
func (h *GitCredentialHandler) GetPublicKey(c *gin.Context) {
	userID := getUserID(c)
	credID := c.Param("id")

	cred, err := h.stores.GitCredentials.GetByID(c.Request.Context(), credID)
	if err != nil || cred.UserID != userID {
		c.JSON(http.StatusNotFound, gin.H{"error": "credential not found"})
		return
	}

	if cred.PublicKey == "" {
		c.JSON(http.StatusNotFound, gin.H{"error": "no public key for this credential type"})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"public_key":  cred.PublicKey,
		"fingerprint": cred.Fingerprint,
	})
}
