package sms

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	"bookra/apps/backend/internal/config"
	"bookra/apps/backend/internal/db"
	"bookra/apps/backend/internal/domain"
	"bookra/apps/backend/internal/notifications"
	"bookra/apps/backend/internal/shared"
)

var (
	ErrSMSNotConfigured     = errors.New("sms is not configured")
	ErrSMSNotEnabled        = errors.New("sms is not enabled for this tenant")
	ErrSMSPlanNotAllowed    = errors.New("sms is only available on pro and business plans")
	ErrSMSLimitReached      = errors.New("monthly sms limit reached")
	ErrSMSInvalidPhone      = errors.New("invalid phone number")
	ErrSMSMissingAPIKey     = errors.New("sms manager api key is missing")
	ErrSMSSendFailed        = errors.New("sms send failed")
	ErrStripeNotConfigured  = errors.New("stripe is not configured for sms billing")
	ErrNoActiveSubscription = errors.New("no active subscription for sms billing")
)

const smsCostCents = 150 // 1.50 CZK

type Service struct {
	cfg     config.Config
	repo    db.Repository
	billing *BillingService
	client  *http.Client
	baseURL string
	apiKey  string
}

func NewService(cfg config.Config, repo db.Repository) *Service {
	s := &Service{
		cfg:     cfg,
		repo:    repo,
		client:  &http.Client{Timeout: 15 * time.Second},
		baseURL: strings.TrimRight(cfg.SMSManagerBaseURL, "/"),
		apiKey:  cfg.SMSManagerAPIKey,
	}
	if cfg.StripeConfigured() && cfg.StripeSMSConfigured() {
		s.billing = NewBillingService(cfg, repo)
	}
	return s
}

func (s *Service) Enabled() bool {
	return s.cfg.SMSConfigured()
}

func (s *Service) IsAvailable(ctx context.Context, tenantID string) (bool, error) {
	if !s.Enabled() {
		return false, nil
	}
	settings, err := s.repo.GetTenantSMSSettings(ctx, tenantID)
	if err != nil {
		return false, err
	}
	return settings.Enabled, nil
}

func (s *Service) canUseSMS(ctx context.Context, tenantID string) error {
	if !s.Enabled() {
		return ErrSMSNotConfigured
	}

	tenant, err := s.repo.GetTenantByID(ctx, tenantID)
	if err != nil {
		return err
	}

	plan := shared.NormalizePlanCode(tenant.PlanCode)
	if plan != "pro" && plan != "business" {
		return ErrSMSPlanNotAllowed
	}

	if tenant.SubscriptionStatus != "active" && tenant.SubscriptionStatus != "trialing" {
		return ErrNoActiveSubscription
	}

	settings, err := s.repo.GetTenantSMSSettings(ctx, tenantID)
	if err != nil {
		return err
	}
	if !settings.Enabled {
		return ErrSMSNotEnabled
	}

	return nil
}

func (s *Service) GetSettings(ctx context.Context, tenantID string) (domain.SMSSettings, error) {
	settings, err := s.repo.GetTenantSMSSettings(ctx, tenantID)
	if err != nil {
		return domain.SMSSettings{}, err
	}

	usage, err := s.repo.GetSMSUsageThisMonth(ctx, tenantID)
	if err != nil {
		return domain.SMSSettings{}, err
	}

	return domain.SMSSettings{
		Enabled:        settings.Enabled,
		SenderName:     settings.SenderName,
		MonthlyLimit:   settings.MonthlyLimit,
		MessagesSent:   usage.MessageCount,
		TotalCostCents: usage.TotalCostCents,
		Available:      s.Enabled(),
	}, nil
}

func (s *Service) UpdateSettings(ctx context.Context, tenantID string, req domain.UpdateSMSSettingsRequest) (domain.SMSSettings, error) {
	tenant, err := s.repo.GetTenantByID(ctx, tenantID)
	if err != nil {
		return domain.SMSSettings{}, err
	}

	plan := shared.NormalizePlanCode(tenant.PlanCode)
	if plan != "pro" && plan != "business" {
		return domain.SMSSettings{}, ErrSMSPlanNotAllowed
	}

	if tenant.SubscriptionStatus != "active" && tenant.SubscriptionStatus != "trialing" {
		return domain.SMSSettings{}, ErrNoActiveSubscription
	}

	if err := s.repo.UpsertTenantSMSSettings(ctx, db.TenantSMSSettingsRecord{
		TenantID:     tenantID,
		Enabled:      req.Enabled,
		SenderName:   strings.TrimSpace(req.SenderName),
		MonthlyLimit: req.MonthlyLimit,
	}); err != nil {
		return domain.SMSSettings{}, err
	}

	return s.GetSettings(ctx, tenantID)
}

func (s *Service) SendMessage(ctx context.Context, tenantID string, req domain.SendSMSRequest) (domain.SendSMSResponse, error) {
	if err := s.canUseSMS(ctx, tenantID); err != nil {
		return domain.SendSMSResponse{}, err
	}

	phone := normalizePhone(req.To)
	if phone == "" {
		return domain.SendSMSResponse{}, ErrSMSInvalidPhone
	}

	// Check monthly limit
	settings, err := s.repo.GetTenantSMSSettings(ctx, tenantID)
	if err != nil {
		return domain.SendSMSResponse{}, err
	}
	if settings.MonthlyLimit > 0 {
		usage, err := s.repo.GetSMSUsageThisMonth(ctx, tenantID)
		if err != nil {
			return domain.SendSMSResponse{}, err
		}
		if usage.MessageCount >= settings.MonthlyLimit {
			return domain.SendSMSResponse{}, ErrSMSLimitReached
		}
	}

	// Send via SMS Manager
	resp, err := s.sendToSMSManager(ctx, phone, req.Body, settings.SenderName)
	if err != nil {
		return domain.SendSMSResponse{}, err
	}

	// Extract message ID from accepted recipients
	messageID := ""
	if len(resp.Accepted) > 0 {
		messageID = resp.Accepted[0].MessageID
	}

	// Log usage locally (Stripe billing happens once at month-end)
	logID, err := s.repo.CreateSMSUsageLog(ctx, db.SMSUsageLogRecord{
		TenantID:          tenantID,
		RecipientPhone:    phone,
		MessageBody:       req.Body,
		ExternalMessageID: messageID,
		ExternalRequestID: resp.RequestID,
		Status:            "sent",
		CostCents:         smsCostCents,
		SentAt:            time.Now().UTC(),
	})
	if err != nil {
		return domain.SendSMSResponse{}, fmt.Errorf("failed to log sms usage: %w", err)
	}

	return domain.SendSMSResponse{
		LogID:     logID,
		MessageID: messageID,
		RequestID: resp.RequestID,
		Status:    "sent",
		CostCents: smsCostCents,
	}, nil
}

func (s *Service) GetUsage(ctx context.Context, tenantID string, yearMonth string) (domain.SMSUsageReport, error) {
	report, err := s.repo.GetSMSUsageForMonth(ctx, tenantID, yearMonth)
	if err != nil {
		return domain.SMSUsageReport{}, err
	}
	return domain.SMSUsageReport{
		YearMonth:      report.YearMonth,
		MessageCount:   report.MessageCount,
		TotalCostCents: report.TotalCostCents,
	}, nil
}

func (s *Service) GetUsageHistory(ctx context.Context, tenantID string, limit int) ([]domain.SMSUsageLog, error) {
	if limit <= 0 {
		limit = 50
	}
	records, err := s.repo.ListSMSUsageLogs(ctx, tenantID, limit)
	if err != nil {
		return nil, err
	}

	logs := make([]domain.SMSUsageLog, len(records))
	for i, r := range records {
		logs[i] = domain.SMSUsageLog{
			ID:             r.ID,
			RecipientPhone: r.RecipientPhone,
			MessageBody:    r.MessageBody,
			Status:         r.Status,
			CostCents:      r.CostCents,
			CreatedAt:      r.CreatedAt,
		}
	}
	return logs, nil
}

func (s *Service) GetMonthlyReports(ctx context.Context, tenantID string, limit int) ([]domain.SMSUsageReport, error) {
	if limit <= 0 {
		limit = 12
	}
	records, err := s.repo.ListSMSMonthlyReports(ctx, tenantID, limit)
	if err != nil {
		return nil, err
	}

	reports := make([]domain.SMSUsageReport, len(records))
	for i, r := range records {
		reports[i] = domain.SMSUsageReport{
			YearMonth:       r.YearMonth,
			MessageCount:    r.MessageCount,
			TotalCostCents:  r.TotalCostCents,
			StripeInvoiceID: r.StripeInvoiceID,
			InvoiceSentAt:   r.InvoiceSentAt,
		}
	}
	return reports, nil
}

// GenerateMonthlyInvoices creates/finalizes monthly invoices for SMS usage
func (s *Service) GenerateMonthlyInvoices(ctx context.Context, yearMonth string, notificationSvc *notifications.Service) (domain.SMSInvoiceBatchResponse, error) {
	if yearMonth == "" {
		now := time.Now().UTC()
		yearMonth = fmt.Sprintf("%04d-%02d", now.Year(), now.Month())
	}

	response := domain.SMSInvoiceBatchResponse{YearMonth: yearMonth}

	// Get all tenants with SMS enabled that have usage this month
	tenants, err := s.repo.ListTenantsWithSMSUsage(ctx, yearMonth)
	if err != nil {
		return response, err
	}

	for _, t := range tenants {
		report, err := s.repo.GetSMSUsageForMonth(ctx, t.ID, yearMonth)
		if err != nil {
			response.FailedCount++
			continue
		}

		if report.MessageCount == 0 {
			continue
		}

		// Create Stripe InvoiceItem for total monthly SMS usage
		// This adds one line to the customer's next invoice: all messages together
		stripeInvoiceItemID := ""
		if s.billing != nil {
			customerID := ""
			if t.BillingCustomerID != nil {
				customerID = *t.BillingCustomerID
			}
			if customerID != "" {
				currency := "czk"
				if t.BillingProvider != "" {
					// Try to infer currency from billing snapshot if available
					snap, _ := s.repo.GetSubscriptionSnapshot(ctx, t.ID)
					if snap.Currency != "" {
						currency = snap.Currency
					}
				}
				itemID, err := s.billing.CreateMonthlyInvoiceItem(ctx, customerID, currency, yearMonth, report.MessageCount, report.TotalCostCents)
				if err == nil {
					stripeInvoiceItemID = itemID
				}
			}
		}

		if err := s.repo.UpsertSMSMonthlyReport(ctx, db.SMSMonthlyReportRecord{
			TenantID:        t.ID,
			YearMonth:       yearMonth,
			MessageCount:    report.MessageCount,
			TotalCostCents:  report.TotalCostCents,
			StripeInvoiceID: stripeInvoiceItemID,
		}); err != nil {
			response.FailedCount++
			continue
		}

		// Send usage summary email
		if notificationSvc != nil {
			_ = s.sendUsageSummaryEmail(ctx, t, report, notificationSvc)
		}

		response.ProcessedCount++
	}

	return response, nil
}

func (s *Service) sendUsageSummaryEmail(ctx context.Context, tenant db.TenantRecord, report db.SMSMonthlyReportRecord, svc *notifications.Service) error {
	// Get brand profile for email styling
	brand, _ := s.repo.GetBrandProfile(ctx, tenant.ID)

	data := notifications.SMSUsageEmailData{
		TenantName:     brand.Name,
		TenantSlug:     tenant.Slug,
		BusinessEmail:  "",
		YearMonth:      report.YearMonth,
		MessageCount:   report.MessageCount,
		TotalCostCents: report.TotalCostCents,
		Locale:         tenant.Locale,
	}

	// Find owner email
	membership, err := s.repo.GetTenantMembershipByUserID(ctx, tenant.ID)
	if err == nil {
		user, err := s.repo.GetUserByID(ctx, membership.UserID)
		if err == nil {
			data.BusinessEmail = user.Email
		}
	}

	if data.BusinessEmail == "" {
		return errors.New("no business email found")
	}

	msg := notifications.RenderSMSUsageEmail(data)
	_, err = svc.SendRawEmail(ctx, msg)
	if err == nil {
		_ = s.repo.MarkSMSReportInvoiceSent(ctx, report.TenantID, report.YearMonth)
	}
	return err
}

// --- SMS Manager API client ---

type smsManagerMessage struct {
	Body string `json:"body"`
	To   []struct {
		PhoneNumber string `json:"phone_number"`
	} `json:"to"`
	Tag string `json:"tag,omitempty"`
}

type smsManagerResponse struct {
	RequestID string `json:"request_id"`
	Accepted  []struct {
		Key       string `json:"key"`
		MessageID string `json:"message_id"`
	} `json:"accepted"`
	Rejected []struct {
		Key     string `json:"key"`
		Message string `json:"message,omitempty"`
	} `json:"rejected"`
}

func (s *Service) sendToSMSManager(ctx context.Context, phone, body, senderName string) (smsManagerResponse, error) {
	if s.apiKey == "" {
		return smsManagerResponse{}, ErrSMSMissingAPIKey
	}

	payload := smsManagerMessage{
		Body: body,
		To: []struct {
			PhoneNumber string `json:"phone_number"`
		}{{PhoneNumber: phone}},
		Tag: "transactional",
	}

	jsonBody, err := json.Marshal(payload)
	if err != nil {
		return smsManagerResponse{}, err
	}

	req, err := http.NewRequestWithContext(ctx, "POST", s.baseURL+"/message", bytes.NewReader(jsonBody))
	if err != nil {
		return smsManagerResponse{}, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("x-api-key", s.apiKey)

	resp, err := s.client.Do(req)
	if err != nil {
		return smsManagerResponse{}, err
	}
	defer resp.Body.Close()

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return smsManagerResponse{}, err
	}

	if resp.StatusCode != http.StatusOK {
		return smsManagerResponse{}, fmt.Errorf("%w: status=%d body=%s", ErrSMSSendFailed, resp.StatusCode, string(respBody))
	}

	var result smsManagerResponse
	if err := json.Unmarshal(respBody, &result); err != nil {
		return smsManagerResponse{}, err
	}

	if len(result.Rejected) > 0 && len(result.Accepted) == 0 {
		return result, fmt.Errorf("%w: %v", ErrSMSSendFailed, result.Rejected)
	}

	return result, nil
}

func normalizePhone(phone string) string {
	p := strings.TrimSpace(phone)
	p = strings.ReplaceAll(p, " ", "")
	p = strings.ReplaceAll(p, "-", "")
	p = strings.TrimPrefix(p, "+")
	p = strings.TrimPrefix(p, "00")
	// Czech default if no country code and 9 digits
	if len(p) == 9 {
		p = "420" + p
	}
	return p
}
