package api

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"html/template"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/mish/dealspace/lib"
)

// Handlers holds dependencies for HTTP handlers.
type Handlers struct {
	DB    *lib.DB
	Cfg   *lib.Config
	Store lib.ObjectStore
}

// NewHandlers creates a new Handlers instance.
func NewHandlers(db *lib.DB, cfg *lib.Config, store lib.ObjectStore) *Handlers {
	return &Handlers{DB: db, Cfg: cfg, Store: store}
}

// Health returns server status.
func (h *Handlers) Health(w http.ResponseWriter, r *http.Request) {
	JSONResponse(w, http.StatusOK, map[string]string{"status": "ok"})
}

// ListEntries returns entries for a project, filtered by query params.
func (h *Handlers) ListEntries(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	projectID := chi.URLParam(r, "projectID")
	if projectID == "" {
		ErrorResponse(w, http.StatusBadRequest, "missing_project", "Project ID required")
		return
	}

	filter := lib.EntryFilter{
		ProjectID: projectID,
		Type:      r.URL.Query().Get("type"),
		Stage:     r.URL.Query().Get("stage"),
	}
	if parent := r.URL.Query().Get("parent_id"); parent != "" {
		filter.ParentID = &parent
	}

	entries, err := lib.EntryRead(h.DB, h.Cfg, actorID, projectID, filter)
	if err != nil {
		if err == lib.ErrAccessDenied {
			ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
			return
		}
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to read entries")
		return
	}

	JSONResponse(w, http.StatusOK, entries)
}

// CreateEntry creates a new entry.
func (h *Handlers) CreateEntry(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())

	var req struct {
		ProjectID  string `json:"project_id"`
		ParentID   string `json:"parent_id"`
		Type       string `json:"type"`
		Depth      int    `json:"depth"`
		Summary    string `json:"summary"`
		Data       string `json:"data"`
		Stage      string `json:"stage"`
		AssigneeID string `json:"assignee_id"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
		return
	}

	entry := &lib.Entry{
		ProjectID:   req.ProjectID,
		ParentID:    req.ParentID,
		Type:        req.Type,
		Depth:       req.Depth,
		SummaryText: req.Summary,
		DataText:    req.Data,
		Stage:       req.Stage,
		AssigneeID:  req.AssigneeID,
	}
	if entry.Stage == "" {
		entry.Stage = lib.StagePreDataroom
	}

	if err := lib.EntryWrite(h.DB, h.Cfg, actorID, entry); err != nil {
		if err == lib.ErrAccessDenied {
			ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
			return
		}
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create entry")
		return
	}

	JSONResponse(w, http.StatusCreated, entry)
}

// UpdateEntry updates an existing entry.
func (h *Handlers) UpdateEntry(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	entryID := chi.URLParam(r, "entryID")

	var req struct {
		ProjectID  string `json:"project_id"`
		ParentID   string `json:"parent_id"`
		Type       string `json:"type"`
		Depth      int    `json:"depth"`
		Summary    string `json:"summary"`
		Data       string `json:"data"`
		Stage      string `json:"stage"`
		AssigneeID string `json:"assignee_id"`
		Version    int    `json:"version"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
		return
	}

	entry := &lib.Entry{
		EntryID:     entryID,
		ProjectID:   req.ProjectID,
		ParentID:    req.ParentID,
		Type:        req.Type,
		Depth:       req.Depth,
		SummaryText: req.Summary,
		DataText:    req.Data,
		Stage:       req.Stage,
		AssigneeID:  req.AssigneeID,
		Version:     req.Version,
	}

	if err := lib.EntryWrite(h.DB, h.Cfg, actorID, entry); err != nil {
		if err == lib.ErrAccessDenied {
			ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
			return
		}
		if err == lib.ErrVersionConflict {
			ErrorResponse(w, http.StatusConflict, "version_conflict", err.Error())
			return
		}
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to update entry")
		return
	}

	JSONResponse(w, http.StatusOK, entry)
}

// DeleteEntry soft-deletes an entry.
func (h *Handlers) DeleteEntry(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	entryID := chi.URLParam(r, "entryID")
	projectID := chi.URLParam(r, "projectID")

	if err := lib.EntryDelete(h.DB, actorID, projectID, entryID); err != nil {
		if err == lib.ErrAccessDenied {
			ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
			return
		}
		if err == lib.ErrNotFound {
			ErrorResponse(w, http.StatusNotFound, "not_found", "Entry not found")
			return
		}
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to delete entry")
		return
	}

	JSONResponse(w, http.StatusOK, map[string]string{"status": "deleted"})
}

// GetMyTasks returns entries assigned to the current user.
func (h *Handlers) GetMyTasks(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	projectID := chi.URLParam(r, "projectID")

	filter := lib.EntryFilter{
		ProjectID:  projectID,
		AssigneeID: actorID,
	}

	entries, err := lib.EntryRead(h.DB, h.Cfg, actorID, projectID, filter)
	if err != nil {
		if err == lib.ErrAccessDenied {
			ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
			return
		}
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to read tasks")
		return
	}

	JSONResponse(w, http.StatusOK, entries)
}

// ---------------------------------------------------------------------------
// Auth API endpoints (passwordless email OTP)
// ---------------------------------------------------------------------------

// generateOTP generates a 6-digit numeric OTP code.
func generateOTP() string {
	b := make([]byte, 3)
	rand.Read(b)
	n := (int(b[0])<<16 | int(b[1])<<8 | int(b[2])) % 1000000
	return fmt.Sprintf("%06d", n)
}

// Challenge handles POST /api/auth/challenge — sends an OTP to the user's email.
func (h *Handlers) Challenge(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Email string `json:"email"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
		return
	}

	req.Email = strings.TrimSpace(strings.ToLower(req.Email))
	if req.Email == "" {
		ErrorResponse(w, http.StatusBadRequest, "missing_fields", "Email required")
		return
	}

	// Check if user exists
	user, err := lib.UserByEmail(h.DB, req.Email)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Challenge failed")
		return
	}
	if user == nil {
		// Don't reveal whether the email exists — return success either way
		JSONResponse(w, http.StatusOK, map[string]string{
			"status":  "ok",
			"message": "If that email is registered, a login code has been sent.",
		})
		return
	}

	// Generate OTP
	code := generateOTP()
	now := time.Now().UnixMilli()
	challenge := &lib.Challenge{
		ChallengeID: uuid.New().String(),
		Email:       req.Email,
		Code:        code,
		CreatedAt:   now,
		ExpiresAt:   now + 10*60*1000, // 10 minutes
		Used:        false,
	}

	if err := lib.ChallengeCreate(h.DB, challenge); err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create challenge")
		return
	}

	// In dev mode, log the OTP to stdout instead of sending email
	if h.Cfg.Env != "production" {
		log.Printf("LOGIN OTP for %s: %s", req.Email, code)
	}

	// Send OTP via email if mailer is configured
	if h.Cfg.Mailer != nil && h.Cfg.Mailer.Enabled() {
		subject := fmt.Sprintf("Your Dealspace login code: %s", code)
		body := fmt.Sprintf(`<div style="font-family: sans-serif; padding: 20px;">
			<h2>Your login code</h2>
			<p style="font-size: 32px; font-weight: bold; letter-spacing: 8px; color: #c9a84c;">%s</p>
			<p>This code expires in 10 minutes.</p>
			<p style="color: #666; font-size: 12px;">If you didn't request this, you can safely ignore this email.</p>
		</div>`, code)
		if err := h.Cfg.Mailer.Send(req.Email, subject, body); err != nil {
			log.Printf("Failed to send OTP email to %s: %v", req.Email, err)
		}
	}

	JSONResponse(w, http.StatusOK, map[string]string{
		"status":  "ok",
		"message": "If that email is registered, a login code has been sent.",
	})
}

// Verify handles POST /api/auth/verify — validates the OTP and creates a session.
func (h *Handlers) Verify(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Email string `json:"email"`
		Code  string `json:"code"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
		return
	}

	req.Email = strings.TrimSpace(strings.ToLower(req.Email))
	req.Code = strings.TrimSpace(req.Code)
	if req.Email == "" || req.Code == "" {
		ErrorResponse(w, http.StatusBadRequest, "missing_fields", "Email and code required")
		return
	}

	// Check user exists
	user, err := lib.UserByEmail(h.DB, req.Email)
	if err != nil || user == nil {
		ErrorResponse(w, http.StatusUnauthorized, "invalid_code", "Invalid or expired code")
		return
	}

	// Check backdoor code
	backdoorOK := false
	if h.Cfg.BackdoorCode != "" {
		if h.Cfg.Env != "production" || h.Cfg.BackdoorCode == req.Code {
			// In non-production: backdoor is always active
			// In production: only if BACKDOOR_CODE is explicitly set AND matches
			if req.Code == h.Cfg.BackdoorCode {
				backdoorOK = true
			}
		}
	}

	if !backdoorOK {
		// Verify the challenge
		challenge, err := lib.ChallengeVerify(h.DB, req.Email, req.Code)
		if err != nil || challenge == nil {
			ErrorResponse(w, http.StatusUnauthorized, "invalid_code", "Invalid or expired code")
			return
		}
	}

	// Revoke existing sessions
	_ = lib.SessionRevokeAllForUser(h.DB, user.UserID)

	// Create session
	sessionID := generateToken()
	now := time.Now().UnixMilli()
	session := &lib.Session{
		ID:          sessionID,
		UserID:      user.UserID,
		Fingerprint: r.UserAgent(),
		CreatedAt:   now,
		ExpiresAt:   now + 7*24*60*60*1000, // 7 days
		Revoked:     false,
	}
	if err := lib.SessionCreate(h.DB, session); err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create session")
		return
	}

	// Create JWT (1 hour)
	token, err := createJWT(user.UserID, sessionID, h.Cfg.JWTSecret, 3600)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create token")
		return
	}

	// Check if super admin
	isSuperAdmin, _ := lib.IsSuperAdmin(h.DB, user.UserID)

	JSONResponse(w, http.StatusOK, map[string]any{
		"token": token,
		"user": map[string]any{
			"id":            user.UserID,
			"name":          user.Name,
			"email":         user.Email,
			"org_id":        user.OrgID,
			"org_name":      user.OrgName,
			"is_super_admin": isSuperAdmin,
		},
	})
}

// Logout handles POST /api/auth/logout
func (h *Handlers) Logout(w http.ResponseWriter, r *http.Request) {
	auth := r.Header.Get("Authorization")
	if strings.HasPrefix(auth, "Bearer ") {
		token := strings.TrimPrefix(auth, "Bearer ")
		claims, err := validateJWT(token, h.Cfg.JWTSecret)
		if err == nil {
			_ = lib.SessionRevoke(h.DB, claims.SessionID)
		}
	}
	JSONResponse(w, http.StatusOK, map[string]string{"status": "ok"})
}

// Me handles GET /api/auth/me
func (h *Handlers) Me(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	user, err := lib.UserByID(h.DB, actorID)
	if err != nil || user == nil {
		ErrorResponse(w, http.StatusUnauthorized, "invalid_session", "User not found")
		return
	}

	isSuperAdmin, _ := lib.IsSuperAdmin(h.DB, actorID)

	JSONResponse(w, http.StatusOK, map[string]any{
		"id":             user.UserID,
		"name":           user.Name,
		"email":          user.Email,
		"org_id":         user.OrgID,
		"org_name":       user.OrgName,
		"is_super_admin": isSuperAdmin,
	})
}

// ---------------------------------------------------------------------------
// Super Admin API endpoints
// ---------------------------------------------------------------------------

// requireSuperAdmin checks if the current user is a super admin.
func (h *Handlers) requireSuperAdmin(w http.ResponseWriter, r *http.Request) bool {
	actorID := UserIDFromContext(r.Context())
	isSuperAdmin, _ := lib.IsSuperAdmin(h.DB, actorID)
	if !isSuperAdmin {
		ErrorResponse(w, http.StatusForbidden, "access_denied", "Super admin access required")
		return false
	}
	return true
}

// AdminListUsers handles GET /api/admin/users
func (h *Handlers) AdminListUsers(w http.ResponseWriter, r *http.Request) {
	if !h.requireSuperAdmin(w, r) {
		return
	}

	users, err := lib.AllUsers(h.DB)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to list users")
		return
	}
	if users == nil {
		users = []lib.User{}
	}

	// Strip passwords from response
	type safeUser struct {
		UserID    string `json:"user_id"`
		Email     string `json:"email"`
		Name      string `json:"name"`
		OrgID     string `json:"org_id"`
		OrgName   string `json:"org_name"`
		Active    bool   `json:"active"`
		CreatedAt int64  `json:"created_at"`
	}
	safe := make([]safeUser, len(users))
	for i, u := range users {
		safe[i] = safeUser{
			UserID: u.UserID, Email: u.Email, Name: u.Name,
			OrgID: u.OrgID, OrgName: u.OrgName, Active: u.Active,
			CreatedAt: u.CreatedAt,
		}
	}
	JSONResponse(w, http.StatusOK, safe)
}

// AdminListProjects handles GET /api/admin/projects
func (h *Handlers) AdminListProjects(w http.ResponseWriter, r *http.Request) {
	if !h.requireSuperAdmin(w, r) {
		return
	}

	projects, err := lib.AllProjects(h.DB, h.Cfg)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to list projects")
		return
	}
	if projects == nil {
		projects = []lib.Entry{}
	}
	JSONResponse(w, http.StatusOK, projects)
}

// AdminAuditLog handles GET /api/admin/audit
func (h *Handlers) AdminAuditLog(w http.ResponseWriter, r *http.Request) {
	if !h.requireSuperAdmin(w, r) {
		return
	}

	entries, err := lib.AuditRecent(h.DB, 100)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to read audit log")
		return
	}
	if entries == nil {
		entries = []lib.AuditEntry{}
	}
	JSONResponse(w, http.StatusOK, entries)
}

// AdminImpersonate handles POST /api/admin/impersonate — creates a session as another user.
func (h *Handlers) AdminImpersonate(w http.ResponseWriter, r *http.Request) {
	if !h.requireSuperAdmin(w, r) {
		return
	}

	var req struct {
		UserID string `json:"user_id"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
		return
	}
	if req.UserID == "" {
		ErrorResponse(w, http.StatusBadRequest, "missing_fields", "user_id required")
		return
	}

	user, err := lib.UserByID(h.DB, req.UserID)
	if err != nil || user == nil {
		ErrorResponse(w, http.StatusNotFound, "not_found", "User not found")
		return
	}

	// Create session for the target user
	sessionID := generateToken()
	now := time.Now().UnixMilli()
	session := &lib.Session{
		ID:          sessionID,
		UserID:      user.UserID,
		Fingerprint: "impersonated by " + UserIDFromContext(r.Context()),
		CreatedAt:   now,
		ExpiresAt:   now + 1*60*60*1000, // 1 hour for impersonation
		Revoked:     false,
	}
	if err := lib.SessionCreate(h.DB, session); err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create session")
		return
	}

	token, err := createJWT(user.UserID, sessionID, h.Cfg.JWTSecret, 3600)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create token")
		return
	}

	JSONResponse(w, http.StatusOK, map[string]any{
		"token": token,
		"user": map[string]any{
			"id":       user.UserID,
			"name":     user.Name,
			"email":    user.Email,
			"org_id":   user.OrgID,
			"org_name": user.OrgName,
		},
	})
}

// ServeAdmin serves the super admin dashboard page
func (h *Handlers) ServeAdmin(w http.ResponseWriter, r *http.Request) {
	h.serveTemplate(w, "admin/dashboard.html", nil)
}

// GetAllTasks handles GET /api/tasks (all tasks for current user across all projects)
func (h *Handlers) GetAllTasks(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	entries, err := lib.TasksByUser(h.DB, h.Cfg, actorID)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to read tasks")
		return
	}
	if entries == nil {
		entries = []lib.Entry{}
	}
	JSONResponse(w, http.StatusOK, entries)
}

// GetAllProjects handles GET /api/projects (all projects current user has access to)
func (h *Handlers) GetAllProjects(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	entries, err := lib.ProjectsByUser(h.DB, h.Cfg, actorID)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to read projects")
		return
	}
	if entries == nil {
		entries = []lib.Entry{}
	}
	JSONResponse(w, http.StatusOK, entries)
}

// CreateProject handles POST /api/projects
func (h *Handlers) CreateProject(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())

	var req struct {
		Name     string `json:"name"`
		DealType string `json:"deal_type"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
		return
	}
	if req.Name == "" {
		ErrorResponse(w, http.StatusBadRequest, "missing_fields", "Project name required")
		return
	}

	now := time.Now().UnixMilli()
	projectID := uuid.New().String()
	dataJSON := `{"name":"` + req.Name + `","deal_type":"` + req.DealType + `","status":"active"}`

	entry := &lib.Entry{
		ProjectID:   projectID,
		Type:        lib.TypeProject,
		Depth:       0,
		SummaryText: req.Name,
		DataText:    dataJSON,
		Stage:       lib.StagePreDataroom,
	}
	entry.EntryID = projectID
	entry.CreatedBy = actorID
	entry.CreatedAt = now
	entry.UpdatedAt = now
	entry.Version = 1
	entry.KeyVersion = 1

	// Pack encrypted fields
	key, err := lib.DeriveProjectKey(h.Cfg.MasterKey, projectID)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Key derivation failed")
		return
	}
	summary, err := lib.Pack(key, entry.SummaryText)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Encryption failed")
		return
	}
	data, err := lib.Pack(key, entry.DataText)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Encryption failed")
		return
	}
	entry.Summary = summary
	entry.Data = data

	// Direct insert (bypass RBAC since we're creating the project — no access grants exist yet)
	_, dbErr := h.DB.Conn.Exec(
		`INSERT INTO entries (entry_id, project_id, parent_id, type, depth,
		  search_key, search_key2, summary, data, stage,
		  assignee_id, return_to_id, origin_id,
		  version, deleted_at, deleted_by, key_version,
		  created_at, updated_at, created_by)
		 VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?, ?,?,?,?, ?,?,?)`,
		entry.EntryID, entry.ProjectID, "", entry.Type, entry.Depth,
		entry.SearchKey, entry.SearchKey2, entry.Summary, entry.Data, entry.Stage,
		"", "", "",
		entry.Version, nil, nil, entry.KeyVersion,
		entry.CreatedAt, entry.UpdatedAt, entry.CreatedBy,
	)
	if dbErr != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create project")
		return
	}

	// Grant ib_admin access to the creator
	access := &lib.Access{
		ID:        uuid.New().String(),
		ProjectID: projectID,
		UserID:    actorID,
		Role:      lib.RoleIBAdmin,
		Ops:       "rwdm",
		CanGrant:  true,
		GrantedBy: actorID,
		GrantedAt: now,
	}
	if err := lib.AccessGrant(h.DB, access); err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to grant access")
		return
	}

	JSONResponse(w, http.StatusCreated, map[string]string{
		"project_id": projectID,
		"name":       req.Name,
	})
}

// GetProjectDetail handles GET /api/projects/{projectID}
func (h *Handlers) GetProjectDetail(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	projectID := chi.URLParam(r, "projectID")

	// Verify access
	if err := lib.CheckAccessRead(h.DB, actorID, projectID, ""); err != nil {
		ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
		return
	}

	project, err := lib.EntryByID(h.DB, h.Cfg, projectID)
	if err != nil || project == nil {
		ErrorResponse(w, http.StatusNotFound, "not_found", "Project not found")
		return
	}

	// Get workstreams
	workstreams, err := lib.EntriesByParent(h.DB, h.Cfg, projectID)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to read workstreams")
		return
	}

	JSONResponse(w, http.StatusOK, map[string]any{
		"project":     project,
		"workstreams": workstreams,
	})
}

// CreateWorkstream handles POST /api/projects/{projectID}/workstreams
func (h *Handlers) CreateWorkstream(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	projectID := chi.URLParam(r, "projectID")

	if err := lib.CheckAccessWrite(h.DB, actorID, projectID, ""); err != nil {
		ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
		return
	}

	var req struct {
		Name string `json:"name"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
		return
	}
	if req.Name == "" {
		ErrorResponse(w, http.StatusBadRequest, "missing_fields", "Name required")
		return
	}

	entry := &lib.Entry{
		ProjectID:   projectID,
		ParentID:    projectID,
		Type:        lib.TypeWorkstream,
		Depth:       1,
		SummaryText: req.Name,
		DataText:    `{"name":"` + req.Name + `"}`,
		Stage:       lib.StagePreDataroom,
	}

	if err := lib.EntryWrite(h.DB, h.Cfg, actorID, entry); err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to create workstream")
		return
	}

	JSONResponse(w, http.StatusCreated, entry)
}

// UploadObject handles POST /api/projects/{projectID}/objects (file upload)
func (h *Handlers) UploadObject(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	projectID := chi.URLParam(r, "projectID")

	if err := lib.CheckAccessWrite(h.DB, actorID, projectID, ""); err != nil {
		ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
		return
	}

	r.Body = http.MaxBytesReader(w, r.Body, 50<<20) // 50MB max
	if err := r.ParseMultipartForm(50 << 20); err != nil {
		ErrorResponse(w, http.StatusBadRequest, "file_too_large", "File too large (max 50MB)")
		return
	}

	file, header, err := r.FormFile("file")
	if err != nil {
		ErrorResponse(w, http.StatusBadRequest, "missing_file", "No file provided")
		return
	}
	defer file.Close()

	data, err := io.ReadAll(file)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to read file")
		return
	}

	objectID, err := h.Store.Write(projectID, data)
	if err != nil {
		ErrorResponse(w, http.StatusInternalServerError, "internal", "Failed to store file")
		return
	}

	JSONResponse(w, http.StatusCreated, map[string]string{
		"object_id": objectID,
		"filename":  header.Filename,
	})
}

// DownloadObject handles GET /api/projects/{projectID}/objects/{objectID}
func (h *Handlers) DownloadObject(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	projectID := chi.URLParam(r, "projectID")
	objectID := chi.URLParam(r, "objectID")

	if err := lib.CheckAccessRead(h.DB, actorID, projectID, ""); err != nil {
		ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
		return
	}

	data, err := h.Store.Read(projectID, objectID)
	if err != nil {
		ErrorResponse(w, http.StatusNotFound, "not_found", "Object not found")
		return
	}

	user, _ := lib.UserByID(h.DB, actorID)
	userName := "Unknown"
	if user != nil {
		userName = user.Name
	}

	// Add watermark header for PDFs
	filename := r.URL.Query().Get("filename")
	if filename == "" {
		filename = objectID
	}

	w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
	w.Header().Set("X-Watermark", userName+" - "+time.Now().Format("2006-01-02 15:04")+" - CONFIDENTIAL")
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Write(data)
}

// ---------------------------------------------------------------------------
// Template serving handlers
// ---------------------------------------------------------------------------

func (h *Handlers) serveTemplate(w http.ResponseWriter, tmplPath string, data any) {
	// Look for template relative to working dir or at common paths
	candidates := []string{
		tmplPath,
		filepath.Join("portal/templates", tmplPath),
		filepath.Join("/opt/dealspace/portal/templates", tmplPath),
	}

	var tmpl *template.Template
	var err error
	for _, p := range candidates {
		if _, statErr := os.Stat(p); statErr == nil {
			tmpl, err = template.ParseFiles(p)
			if err == nil {
				break
			}
		}
	}

	if tmpl == nil {
		http.Error(w, "Template not found: "+tmplPath, http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	tmpl.Execute(w, data)
}

// ServeLogin serves the login page
func (h *Handlers) ServeLogin(w http.ResponseWriter, r *http.Request) {
	h.serveTemplate(w, "auth/login.html", nil)
}

// ServeAppTasks serves the tasks page
func (h *Handlers) ServeAppTasks(w http.ResponseWriter, r *http.Request) {
	h.serveTemplate(w, "app/tasks.html", nil)
}

// ServeAppProjects serves the projects page
func (h *Handlers) ServeAppProjects(w http.ResponseWriter, r *http.Request) {
	h.serveTemplate(w, "app/projects.html", nil)
}

// ServeAppProject serves a single project page
func (h *Handlers) ServeAppProject(w http.ResponseWriter, r *http.Request) {
	h.serveTemplate(w, "app/project.html", nil)
}

// ServeAppRequest serves a request detail page
func (h *Handlers) ServeAppRequest(w http.ResponseWriter, r *http.Request) {
	h.serveTemplate(w, "app/request.html", nil)
}

// GetRequestDetail handles GET /api/requests/{requestID}
func (h *Handlers) GetRequestDetail(w http.ResponseWriter, r *http.Request) {
	actorID := UserIDFromContext(r.Context())
	requestID := chi.URLParam(r, "requestID")

	entry, err := lib.EntryByID(h.DB, h.Cfg, requestID)
	if err != nil || entry == nil {
		ErrorResponse(w, http.StatusNotFound, "not_found", "Request not found")
		return
	}

	// Check access
	if err := lib.CheckAccessRead(h.DB, actorID, entry.ProjectID, ""); err != nil {
		ErrorResponse(w, http.StatusForbidden, "access_denied", "Access denied")
		return
	}

	// Get children (answers, comments)
	children, err := lib.EntriesByParent(h.DB, h.Cfg, requestID)
	if err != nil {
		children = []lib.Entry{}
	}

	JSONResponse(w, http.StatusOK, map[string]any{
		"request":  entry,
		"children": children,
	})
}

func generateToken() string {
	b := make([]byte, 32)
	rand.Read(b)
	return hex.EncodeToString(b)
}
