package org

import (
	"context"
	"log/slog"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/core/secrets"
	"github.com/tidbcloud/AgentTeam/internal/store/organization"
)

const organizationInviteTokenTTL = 7 * 24 * time.Hour

func parseOrganizationInviteListLimit(rawLimit string) (int, error) {
	trimmed := strings.TrimSpace(rawLimit)
	if trimmed == "" {
		return organization.DefaultOrganizationInviteListLimit, nil
	}
	limit, err := strconv.Atoi(trimmed)
	if err != nil {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be an integer")
	}
	if limit <= 0 {
		return 0, api.NewHTTPError(http.StatusBadRequest, "limit must be a positive integer")
	}
	if limit > organization.MaxOrganizationInviteListLimit {
		return organization.MaxOrganizationInviteListLimit, nil
	}
	return limit, nil
}

// List handles GET /api/v1/orgs/:orgId/invites.
// It returns all pending invites for the specified organization.
func (handler *InviteHandler) List(r api.AuthenticatedRequest) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	if organizationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "orgId is required")
	}

	limit, err := parseOrganizationInviteListLimit(r.Gin.Query("limit"))
	if err != nil {
		return api.Response{}, err
	}
	options := organization.OrganizationInviteListOptions{
		Limit:  limit,
		Cursor: strings.TrimSpace(r.Gin.Query("cursor")),
	}
	page := organization.OrganizationInvitePage{}
	if pager, ok := orgs.(interface {
		ListOrganizationInvitesForUserPage(context.Context, string, string, organization.OrganizationInviteListOptions) (organization.OrganizationInvitePage, error)
	}); ok {
		var err error
		page, err = pager.ListOrganizationInvitesForUserPage(r.Context(), r.UserID(), organizationID, options)
		if err != nil {
			return api.Response{}, err
		}
	} else {
		invites, err := orgs.ListOrganizationInvitesForUser(r.Context(), r.UserID(), organizationID)
		if err != nil {
			return api.Response{}, err
		}
		page.Invites = invites
	}
	if strings.TrimSpace(r.Gin.Query("limit")) == "" && strings.TrimSpace(r.Gin.Query("cursor")) == "" {
		return api.OK(converter.ToOrgInviteViews(page.Invites)), nil
	}
	return api.OK(converter.ToOrgInvitePageResponse(page)), nil
}

// Create handles POST /api/v1/orgs/:orgId/invites.
// It creates a new organization invite and sends the invitation email.
func (handler *InviteHandler) Create(r api.AuthenticatedJSON[dto.OrganizationInviteCreateRequest]) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	if organizationID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "orgId is required")
	}

	request := r.Body
	email, err := normalizeEmail(request.Email)
	if err != nil {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	token, err := secrets.NewToken(tokenByteLength)
	if err != nil {
		return api.Response{}, err
	}
	invite, err := orgs.CreateOrganizationInviteForUser(
		r.Context(),
		r.UserID(),
		organizationID,
		createOrganizationInviteParams(request, email),
		secrets.HashToken(token),
		time.Now().UTC().Add(organizationInviteTokenTTL),
	)
	if err != nil {
		return api.Response{}, err
	}

	if handler.mailer == nil {
		if err := orgs.RevokeOrganizationInviteForUser(r.Context(), r.UserID(), organizationID, invite.ID); err != nil {
			if handler.logger != nil {
				handler.logger.Error("rollback organization invite after missing mailer", slog.String("inviteId", invite.ID), slog.Any("error", err))
			}
		}
		return api.Response{}, api.NewHTTPErrorWithCode(http.StatusInternalServerError, "Mail service is not configured", errorCodeMailerUnavailable)
	}

	organizations, err := orgs.ListOrganizationsForUser(r.Context(), r.UserID())
	if err != nil {
		return api.Response{}, err
	}
	organizationSummary, ok := findOrganizationSummary(organizations, organizationID)
	if !ok {
		if err := orgs.RevokeOrganizationInviteForUser(r.Context(), r.UserID(), organizationID, invite.ID); err != nil {
			if handler.logger != nil {
				handler.logger.Error("rollback organization invite after missing organization summary", slog.String("inviteId", invite.ID), slog.Any("error", err))
			}
		}
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization not found")
	}

	if err := handler.mailer.SendOrganizationInviteEmail(r.Context(), email, organizationSummary.Name, token); err != nil {
		if handler.logger != nil {
			handler.logger.Error("send organization invite email", slog.String("inviteId", invite.ID), slog.String("email", email), slog.Any("error", err))
		}
		if err := orgs.RevokeOrganizationInviteForUser(r.Context(), r.UserID(), organizationID, invite.ID); err != nil {
			if handler.logger != nil {
				handler.logger.Error("rollback organization invite after mail send failure", slog.String("inviteId", invite.ID), slog.Any("error", err))
			}
		}
		return api.Response{}, api.NewHTTPError(http.StatusServiceUnavailable, "Failed to send invite email")
	}

	return api.OK(converter.ToOrgInviteView(invite)), nil
}

// Delete handles DELETE /api/v1/orgs/:orgId/invites/:inviteId.
// It revokes the specified organization invite.
func (handler *InviteHandler) Delete(r api.AuthenticatedRequest) (api.Response, error) {
	orgs, ok := handler.organizationProvider()
	if !ok {
		return api.Response{}, api.NewHTTPError(http.StatusNotFound, "Organization provider not installed")
	}

	organizationID := strings.TrimSpace(r.Gin.Param("orgId"))
	inviteID := strings.TrimSpace(r.Gin.Param("inviteId"))
	if organizationID == "" || inviteID == "" {
		return api.Response{}, api.NewHTTPError(http.StatusBadRequest, "Invalid request")
	}

	if err := orgs.RevokeOrganizationInviteForUser(r.Context(), r.UserID(), organizationID, inviteID); err != nil {
		return api.Response{}, err
	}
	return api.OK(dto.EmptyResponse{}), nil
}
