package server

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"git.wntrmute.dev/mc/mcias/internal/db"
	"git.wntrmute.dev/mc/mcias/internal/middleware"
	"git.wntrmute.dev/mc/mcias/internal/model"
	"git.wntrmute.dev/mc/mcias/internal/policy"
)

// ---- Tag endpoints ----

type tagsResponse struct {
	Tags []string `json:"tags"`
}

func (s *Server) handleGetTags(w http.ResponseWriter, r *http.Request) {
	acct, ok := s.loadAccount(w, r)
	if !ok {
		return
	}
	tags, err := s.db.GetAccountTags(acct.ID)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}
	if tags == nil {
		tags = []string{}
	}
	writeJSON(w, http.StatusOK, tagsResponse{Tags: tags})
}

type setTagsRequest struct {
	Tags []string `json:"tags"`
}

func (s *Server) handleSetTags(w http.ResponseWriter, r *http.Request) {
	acct, ok := s.loadAccount(w, r)
	if !ok {
		return
	}
	var req setTagsRequest
	if !decodeJSON(w, r, &req) {
		return
	}

	// Validate tags: each must be non-empty.
	for _, tag := range req.Tags {
		if tag == "" {
			middleware.WriteError(w, http.StatusBadRequest, "tag values must not be empty", "bad_request")
			return
		}
	}

	if err := s.db.SetAccountTags(acct.ID, req.Tags); err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}

	// Determine actor for audit log.
	claims := middleware.ClaimsFromContext(r.Context())
	var actorID *int64
	if claims != nil {
		if actor, err := s.db.GetAccountByUUID(claims.Subject); err == nil {
			actorID = &actor.ID
		}
	}

	s.writeAudit(r, model.EventTagAdded, actorID, &acct.ID,
		fmt.Sprintf(`{"account":%q,"tags":%s}`, acct.UUID, marshalStringSlice(req.Tags)))

	tags, err := s.db.GetAccountTags(acct.ID)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}
	if tags == nil {
		tags = []string{}
	}
	writeJSON(w, http.StatusOK, tagsResponse{Tags: tags})
}

// ---- Policy rule endpoints ----

type policyRuleResponse struct {
	CreatedAt   string          `json:"created_at"`
	UpdatedAt   string          `json:"updated_at"`
	NotBefore   *string         `json:"not_before,omitempty"`
	ExpiresAt   *string         `json:"expires_at,omitempty"`
	Description string          `json:"description"`
	RuleBody    policy.RuleBody `json:"rule"`
	ID          int64           `json:"id"`
	Priority    int             `json:"priority"`
	Enabled     bool            `json:"enabled"`
}

func policyRuleToResponse(rec *model.PolicyRuleRecord) (policyRuleResponse, error) {
	var body policy.RuleBody
	if err := json.Unmarshal([]byte(rec.RuleJSON), &body); err != nil {
		return policyRuleResponse{}, fmt.Errorf("decode rule body: %w", err)
	}
	resp := policyRuleResponse{
		ID:          rec.ID,
		Priority:    rec.Priority,
		Description: rec.Description,
		RuleBody:    body,
		Enabled:     rec.Enabled,
		CreatedAt:   rec.CreatedAt.Format(time.RFC3339),
		UpdatedAt:   rec.UpdatedAt.Format(time.RFC3339),
	}
	if rec.NotBefore != nil {
		s := rec.NotBefore.UTC().Format(time.RFC3339)
		resp.NotBefore = &s
	}
	if rec.ExpiresAt != nil {
		s := rec.ExpiresAt.UTC().Format(time.RFC3339)
		resp.ExpiresAt = &s
	}
	return resp, nil
}

func (s *Server) handleListPolicyRules(w http.ResponseWriter, _ *http.Request) {
	rules, err := s.db.ListPolicyRules(false)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}
	resp := make([]policyRuleResponse, 0, len(rules))
	for _, r := range rules {
		rv, err := policyRuleToResponse(r)
		if err != nil {
			middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
			return
		}
		resp = append(resp, rv)
	}
	writeJSON(w, http.StatusOK, resp)
}

type createPolicyRuleRequest struct {
	Description string          `json:"description"`
	NotBefore   *string         `json:"not_before,omitempty"`
	ExpiresAt   *string         `json:"expires_at,omitempty"`
	Rule        policy.RuleBody `json:"rule"`
	Priority    int             `json:"priority"`
}

func (s *Server) handleCreatePolicyRule(w http.ResponseWriter, r *http.Request) {
	var req createPolicyRuleRequest
	if !decodeJSON(w, r, &req) {
		return
	}

	if req.Description == "" {
		middleware.WriteError(w, http.StatusBadRequest, "description is required", "bad_request")
		return
	}
	if req.Rule.Effect != policy.Allow && req.Rule.Effect != policy.Deny {
		middleware.WriteError(w, http.StatusBadRequest, "rule.effect must be 'allow' or 'deny'", "bad_request")
		return
	}

	priority := req.Priority
	if priority == 0 {
		priority = 100 // default
	}

	// Parse optional time-scoped validity window.
	var notBefore, expiresAt *time.Time
	if req.NotBefore != nil {
		t, err := time.Parse(time.RFC3339, *req.NotBefore)
		if err != nil {
			middleware.WriteError(w, http.StatusBadRequest, "not_before must be RFC3339", "bad_request")
			return
		}
		notBefore = &t
	}
	if req.ExpiresAt != nil {
		t, err := time.Parse(time.RFC3339, *req.ExpiresAt)
		if err != nil {
			middleware.WriteError(w, http.StatusBadRequest, "expires_at must be RFC3339", "bad_request")
			return
		}
		expiresAt = &t
	}
	if notBefore != nil && expiresAt != nil && !expiresAt.After(*notBefore) {
		middleware.WriteError(w, http.StatusBadRequest, "expires_at must be after not_before", "bad_request")
		return
	}

	ruleJSON, err := json.Marshal(req.Rule)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}

	claims := middleware.ClaimsFromContext(r.Context())
	var createdBy *int64
	if claims != nil {
		if actor, err := s.db.GetAccountByUUID(claims.Subject); err == nil {
			createdBy = &actor.ID
		}
	}

	rec, err := s.db.CreatePolicyRule(req.Description, priority, string(ruleJSON), createdBy, notBefore, expiresAt)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}

	s.writeAudit(r, model.EventPolicyRuleCreated, createdBy, nil,
		fmt.Sprintf(`{"rule_id":%d,"description":%q}`, rec.ID, rec.Description))

	// Reload the in-memory engine so the new rule takes effect immediately.
	s.reloadPolicyEngine()

	rv, err := policyRuleToResponse(rec)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}
	writeJSON(w, http.StatusCreated, rv)
}

func (s *Server) handleGetPolicyRule(w http.ResponseWriter, r *http.Request) {
	rec, ok := s.loadPolicyRule(w, r)
	if !ok {
		return
	}
	rv, err := policyRuleToResponse(rec)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}
	writeJSON(w, http.StatusOK, rv)
}

type updatePolicyRuleRequest struct {
	Description    *string          `json:"description,omitempty"`
	NotBefore      *string          `json:"not_before,omitempty"`
	ExpiresAt      *string          `json:"expires_at,omitempty"`
	Rule           *policy.RuleBody `json:"rule,omitempty"`
	Priority       *int             `json:"priority,omitempty"`
	Enabled        *bool            `json:"enabled,omitempty"`
	ClearNotBefore *bool            `json:"clear_not_before,omitempty"`
	ClearExpiresAt *bool            `json:"clear_expires_at,omitempty"`
}

func (s *Server) handleUpdatePolicyRule(w http.ResponseWriter, r *http.Request) {
	rec, ok := s.loadPolicyRule(w, r)
	if !ok {
		return
	}
	var req updatePolicyRuleRequest
	if !decodeJSON(w, r, &req) {
		return
	}

	// Validate effect if rule body is being updated.
	var ruleJSON *string
	if req.Rule != nil {
		if req.Rule.Effect != policy.Allow && req.Rule.Effect != policy.Deny {
			middleware.WriteError(w, http.StatusBadRequest, "rule.effect must be 'allow' or 'deny'", "bad_request")
			return
		}
		b, err := json.Marshal(req.Rule)
		if err != nil {
			middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
			return
		}
		js := string(b)
		ruleJSON = &js
	}

	// Parse optional time-scoped validity window updates.
	// Double-pointer semantics: nil = no change, non-nil→nil = clear, non-nil→non-nil = set.
	var notBefore, expiresAt **time.Time
	if req.ClearNotBefore != nil && *req.ClearNotBefore {
		var nilTime *time.Time
		notBefore = &nilTime // non-nil outer, nil inner → set to NULL
	} else if req.NotBefore != nil {
		t, err := time.Parse(time.RFC3339, *req.NotBefore)
		if err != nil {
			middleware.WriteError(w, http.StatusBadRequest, "not_before must be RFC3339", "bad_request")
			return
		}
		tp := &t
		notBefore = &tp
	}
	if req.ClearExpiresAt != nil && *req.ClearExpiresAt {
		var nilTime *time.Time
		expiresAt = &nilTime // non-nil outer, nil inner → set to NULL
	} else if req.ExpiresAt != nil {
		t, err := time.Parse(time.RFC3339, *req.ExpiresAt)
		if err != nil {
			middleware.WriteError(w, http.StatusBadRequest, "expires_at must be RFC3339", "bad_request")
			return
		}
		tp := &t
		expiresAt = &tp
	}

	if err := s.db.UpdatePolicyRule(rec.ID, req.Description, req.Priority, ruleJSON, notBefore, expiresAt); err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}

	if req.Enabled != nil {
		if err := s.db.SetPolicyRuleEnabled(rec.ID, *req.Enabled); err != nil {
			middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
			return
		}
	}

	claims := middleware.ClaimsFromContext(r.Context())
	var actorID *int64
	if claims != nil {
		if actor, err := s.db.GetAccountByUUID(claims.Subject); err == nil {
			actorID = &actor.ID
		}
	}
	s.writeAudit(r, model.EventPolicyRuleUpdated, actorID, nil,
		fmt.Sprintf(`{"rule_id":%d}`, rec.ID))

	// Reload the in-memory engine so rule changes take effect immediately.
	s.reloadPolicyEngine()

	updated, err := s.db.GetPolicyRule(rec.ID)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}
	rv, err := policyRuleToResponse(updated)
	if err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}
	writeJSON(w, http.StatusOK, rv)
}

func (s *Server) handleDeletePolicyRule(w http.ResponseWriter, r *http.Request) {
	rec, ok := s.loadPolicyRule(w, r)
	if !ok {
		return
	}
	if err := s.db.DeletePolicyRule(rec.ID); err != nil {
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return
	}

	claims := middleware.ClaimsFromContext(r.Context())
	var actorID *int64
	if claims != nil {
		if actor, err := s.db.GetAccountByUUID(claims.Subject); err == nil {
			actorID = &actor.ID
		}
	}
	s.writeAudit(r, model.EventPolicyRuleDeleted, actorID, nil,
		fmt.Sprintf(`{"rule_id":%d,"description":%q}`, rec.ID, rec.Description))

	// Reload the in-memory engine so the deleted rule is removed immediately.
	s.reloadPolicyEngine()

	w.WriteHeader(http.StatusNoContent)
}

// loadPolicyRule retrieves a policy rule by the {id} path parameter.
func (s *Server) loadPolicyRule(w http.ResponseWriter, r *http.Request) (*model.PolicyRuleRecord, bool) {
	idStr := r.PathValue("id")
	if idStr == "" {
		middleware.WriteError(w, http.StatusBadRequest, "rule id is required", "bad_request")
		return nil, false
	}
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		middleware.WriteError(w, http.StatusBadRequest, "rule id must be an integer", "bad_request")
		return nil, false
	}
	rec, err := s.db.GetPolicyRule(id)
	if err != nil {
		if errors.Is(err, db.ErrNotFound) {
			middleware.WriteError(w, http.StatusNotFound, "policy rule not found", "not_found")
			return nil, false
		}
		middleware.WriteError(w, http.StatusInternalServerError, "internal error", "internal_error")
		return nil, false
	}
	return rec, true
}

// marshalStringSlice encodes a string slice as a compact JSON array.
// Used for audit log details — never includes credential material.
func marshalStringSlice(ss []string) string {
	b, _ := json.Marshal(ss)
	return string(b)
}
