package businessapi

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

	"git.warky.dev/wdevs/whatshooked/pkg/config"
	"git.warky.dev/wdevs/whatshooked/pkg/events"
	"git.warky.dev/wdevs/whatshooked/pkg/logging"

	"go.mau.fi/whatsmeow/types"
)

const defaultBusinessAPIMediaTimeout = 5 * time.Minute

// Client represents a WhatsApp Business API client
type Client struct {
	id              string
	phoneNumber     string
	config          config.BusinessAPIConfig
	httpClient      *http.Client
	eventBus        *events.EventBus
	mediaConfig     config.MediaConfig
	connected       bool
	wabaID          string                       // WhatsApp Business Account ID, resolved at connect time
	onWABAResolved  func(accountID, wabaID string) // called when WABA ID is resolved for the first time
	mu              sync.RWMutex
}

// NewClient creates a new Business API client
func NewClient(cfg config.WhatsAppConfig, eventBus *events.EventBus, mediaConfig config.MediaConfig, onWABAResolved func(accountID, wabaID string)) (*Client, error) {
	if cfg.Type != "business-api" {
		return nil, fmt.Errorf("invalid client type for business-api: %s", cfg.Type)
	}

	if cfg.BusinessAPI == nil {
		return nil, fmt.Errorf("business_api configuration is required for business-api type")
	}

	// Validate required fields
	if cfg.BusinessAPI.PhoneNumberID == "" {
		return nil, fmt.Errorf("phone_number_id is required")
	}
	if cfg.BusinessAPI.AccessToken == "" {
		return nil, fmt.Errorf("access_token is required")
	}

	// Set default API version
	if cfg.BusinessAPI.APIVersion == "" {
		cfg.BusinessAPI.APIVersion = "v21.0"
	}

	return &Client{
		id:          cfg.ID,
		phoneNumber: cfg.PhoneNumber,
		config:      *cfg.BusinessAPI,
		httpClient: &http.Client{
			Timeout: defaultBusinessAPIMediaTimeout,
		},
		eventBus:       eventBus,
		mediaConfig:    mediaConfig,
		connected:      false,
		onWABAResolved: onWABAResolved,
	}, nil
}

// Connect validates the Business API credentials
func (c *Client) Connect(ctx context.Context) error {
	logging.Info("Validating WhatsApp Business API credentials", "account_id", c.id)

	// Step 1: Validate token and check permissions
	tokenInfo, err := c.validateToken(ctx)
	if err != nil {
		c.eventBus.Publish(events.WhatsAppPairFailedEvent(ctx, c.id, err))
		return fmt.Errorf("token validation failed: %w", err)
	}

	// Log token information
	logging.Info("Access token validated",
		"account_id", c.id,
		"token_type", tokenInfo.Type,
		"app", tokenInfo.Application,
		"app_id", tokenInfo.AppID,
		"expires", c.formatExpiry(tokenInfo.ExpiresAt),
		"scopes", strings.Join(tokenInfo.Scopes, ", "))

	// Check for required permissions
	requiredScopes := []string{"whatsapp_business_management", "whatsapp_business_messaging"}
	missingScopes := c.checkMissingScopes(tokenInfo.Scopes, requiredScopes)
	if len(missingScopes) > 0 {
		err := fmt.Errorf("token missing required permissions: %s", strings.Join(missingScopes, ", "))
		logging.Error("Insufficient token permissions",
			"account_id", c.id,
			"missing_scopes", strings.Join(missingScopes, ", "),
			"current_scopes", strings.Join(tokenInfo.Scopes, ", "))
		c.eventBus.Publish(events.WhatsAppPairFailedEvent(ctx, c.id, err))
		return err
	}

	// Step 2: Get phone number details
	phoneDetails, err := c.getPhoneNumberDetails(ctx)
	if err != nil {
		c.eventBus.Publish(events.WhatsAppPairFailedEvent(ctx, c.id, err))
		return fmt.Errorf("failed to get phone number details: %w", err)
	}

	// Log phone number information
	logging.Info("Phone number details retrieved",
		"account_id", c.id,
		"phone_number_id", phoneDetails.ID,
		"display_number", phoneDetails.DisplayPhoneNumber,
		"verified_name", phoneDetails.VerifiedName,
		"verification_status", phoneDetails.CodeVerificationStatus,
		"quality_rating", phoneDetails.QualityRating,
		"throughput_level", phoneDetails.Throughput.Level)

	// Warn if phone number is not verified
	if phoneDetails.CodeVerificationStatus != "VERIFIED" {
		logging.Warn("Phone number is not verified - messaging capabilities may be limited",
			"account_id", c.id,
			"status", phoneDetails.CodeVerificationStatus)
	}

	// Step 3: Resolve the WhatsApp Business Account (WABA) ID from the phone number.
	// The WABA ID is required for phone number listing; it differs from the Facebook Business Manager ID.
	// Use cached value from config if already known.
	if c.config.WABAId != "" {
		c.wabaID = c.config.WABAId
		logging.Info("WhatsApp Business Account ID loaded from config", "account_id", c.id, "waba_id", c.wabaID)
	} else if wabaID, err := c.fetchWABAID(ctx); err != nil {
		logging.Warn("Failed to resolve WABA ID (non-critical)", "account_id", c.id, "error", err)
	} else {
		c.wabaID = wabaID
		c.config.WABAId = wabaID
		logging.Info("WhatsApp Business Account ID resolved", "account_id", c.id, "waba_id", wabaID)
		if c.onWABAResolved != nil {
			c.onWABAResolved(c.id, wabaID)
		}
	}

	// Step 4: Get business account details (if business_account_id is provided)
	if c.config.BusinessAccountID != "" {
		businessDetails, err := c.getBusinessAccountDetails(ctx)
		if err != nil {
			logging.Warn("Failed to get business account details (non-critical)",
				"account_id", c.id,
				"business_account_id", c.config.BusinessAccountID,
				"error", err)
		} else {
			logging.Info("Business account details retrieved",
				"account_id", c.id,
				"business_account_id", businessDetails.ID,
				"business_name", businessDetails.Name,
				"timezone_id", businessDetails.TimezoneID)
		}
	}

	c.mu.Lock()
	c.connected = true
	c.mu.Unlock()

	logging.Info("Business API client connected successfully",
		"account_id", c.id,
		"phone", phoneDetails.DisplayPhoneNumber,
		"verified_name", phoneDetails.VerifiedName)
	c.eventBus.Publish(events.WhatsAppConnectedEvent(ctx, c.id, phoneDetails.DisplayPhoneNumber))
	return nil
}

// Disconnect closes the Business API client
func (c *Client) Disconnect() error {
	c.mu.Lock()
	c.connected = false
	c.mu.Unlock()

	logging.Info("Business API client disconnected", "account_id", c.id)
	return nil
}

// IsConnected returns whether the client is connected
func (c *Client) IsConnected() bool {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.connected
}

// GetID returns the client ID
func (c *Client) GetID() string {
	return c.id
}

// GetPhoneNumber returns the phone number
func (c *Client) GetPhoneNumber() string {
	return c.phoneNumber
}

// GetType returns the client type
func (c *Client) GetType() string {
	return "business-api"
}

// SendTextMessage sends a text message via Business API
func (c *Client) SendTextMessage(ctx context.Context, jid types.JID, text string) (string, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	// Convert JID to phone number
	phoneNumber := jidToPhoneNumber(jid)

	// Create request
	reqBody := SendMessageRequest{
		MessagingProduct: "whatsapp",
		To:               phoneNumber,
		Type:             "text",
		Text: &TextObject{
			Body: text,
		},
	}

	messageID, err := c.sendMessage(ctx, reqBody)
	if err != nil {
		c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, text, err))
		return "", err
	}

	logging.Debug("Message sent via Business API", "account_id", c.id, "to", phoneNumber)
	c.eventBus.Publish(events.MessageSentEvent(ctx, c.id, messageID, phoneNumber, text))
	return messageID, nil
}

// SendImage sends an image message via Business API
func (c *Client) SendImage(ctx context.Context, jid types.JID, imageData []byte, mimeType string, caption string) (string, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	phoneNumber := jidToPhoneNumber(jid)

	// Upload media first
	mediaID, err := c.uploadMedia(ctx, imageData, mimeType)
	if err != nil {
		c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, caption, err))
		return "", fmt.Errorf("failed to upload image: %w", err)
	}

	// Send message with media ID
	reqBody := SendMessageRequest{
		MessagingProduct: "whatsapp",
		To:               phoneNumber,
		Type:             "image",
		Image: &MediaObject{
			ID:      mediaID,
			Caption: caption,
		},
	}

	messageID, err := c.sendMessage(ctx, reqBody)
	if err != nil {
		c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, caption, err))
		return "", err
	}

	logging.Debug("Image sent via Business API", "account_id", c.id, "to", phoneNumber)
	c.eventBus.Publish(events.MessageSentEvent(ctx, c.id, messageID, phoneNumber, caption))
	return messageID, nil
}

// SendVideo sends a video message via Business API
func (c *Client) SendVideo(ctx context.Context, jid types.JID, videoData []byte, mimeType string, caption string) (string, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	phoneNumber := jidToPhoneNumber(jid)

	// Upload media first
	mediaID, err := c.uploadMedia(ctx, videoData, mimeType)
	if err != nil {
		c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, caption, err))
		return "", fmt.Errorf("failed to upload video: %w", err)
	}

	// Send message with media ID
	reqBody := SendMessageRequest{
		MessagingProduct: "whatsapp",
		To:               phoneNumber,
		Type:             "video",
		Video: &MediaObject{
			ID:      mediaID,
			Caption: caption,
		},
	}

	messageID, err := c.sendMessage(ctx, reqBody)
	if err != nil {
		c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, caption, err))
		return "", err
	}

	logging.Debug("Video sent via Business API", "account_id", c.id, "to", phoneNumber)
	c.eventBus.Publish(events.MessageSentEvent(ctx, c.id, messageID, phoneNumber, caption))
	return messageID, nil
}

// SendDocument sends a document message via Business API
func (c *Client) SendDocument(ctx context.Context, jid types.JID, documentData []byte, mimeType string, filename string, caption string) (string, error) {
	if ctx == nil {
		ctx = context.Background()
	}

	phoneNumber := jidToPhoneNumber(jid)

	// Upload media first
	mediaID, err := c.uploadMedia(ctx, documentData, mimeType)
	if err != nil {
		c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, caption, err))
		return "", fmt.Errorf("failed to upload document: %w", err)
	}

	// Send message with media ID
	reqBody := SendMessageRequest{
		MessagingProduct: "whatsapp",
		To:               phoneNumber,
		Type:             "document",
		Document: &DocumentObject{
			ID:       mediaID,
			Caption:  caption,
			Filename: filename,
		},
	}

	messageID, err := c.sendMessage(ctx, reqBody)
	if err != nil {
		c.eventBus.Publish(events.MessageFailedEvent(ctx, c.id, phoneNumber, caption, err))
		return "", err
	}

	logging.Debug("Document sent via Business API", "account_id", c.id, "to", phoneNumber, "filename", filename)
	c.eventBus.Publish(events.MessageSentEvent(ctx, c.id, messageID, phoneNumber, caption))
	return messageID, nil
}

// sendMessage sends a message request to the Business API
func (c *Client) sendMessage(ctx context.Context, reqBody SendMessageRequest) (string, error) {
	url := fmt.Sprintf("https://graph.facebook.com/%s/%s/messages",
		c.config.APIVersion,
		c.config.PhoneNumberID)

	jsonData, err := json.Marshal(reqBody)
	if err != nil {
		return "", fmt.Errorf("failed to marshal request: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return "", fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return "", fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		var errResp ErrorResponse
		if err := json.Unmarshal(body, &errResp); err == nil {
			return "", fmt.Errorf("API error: %s (code: %d)", errResp.Error.Message, errResp.Error.Code)
		}
		return "", fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
	}

	var sendResp SendMessageResponse
	if err := json.Unmarshal(body, &sendResp); err != nil {
		return "", fmt.Errorf("failed to parse response: %w", err)
	}

	if len(sendResp.Messages) == 0 {
		return "", fmt.Errorf("no message ID in response")
	}

	return sendResp.Messages[0].ID, nil
}

// jidToPhoneNumber converts a WhatsApp JID to E.164 phone number format
func jidToPhoneNumber(jid types.JID) string {
	// JID format is like "27123456789@s.whatsapp.net"
	// Extract the phone number part before @
	phone := jid.User

	// Ensure it starts with + for E.164
	if !strings.HasPrefix(phone, "+") {
		phone = "+" + phone
	}

	return phone
}

// validateToken validates the access token and returns token information
func (c *Client) validateToken(ctx context.Context) (*TokenDebugData, error) {
	url := fmt.Sprintf("https://graph.facebook.com/%s/debug_token?input_token=%s",
		c.config.APIVersion,
		c.config.AccessToken)

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create token validation request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to validate token: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read token validation response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		var errResp ErrorResponse
		if err := json.Unmarshal(body, &errResp); err == nil {
			return nil, fmt.Errorf("token validation failed: %s (code: %d)", errResp.Error.Message, errResp.Error.Code)
		}
		return nil, fmt.Errorf("token validation returned status %d: %s", resp.StatusCode, string(body))
	}

	var tokenResp TokenDebugResponse
	if err := json.Unmarshal(body, &tokenResp); err != nil {
		return nil, fmt.Errorf("failed to parse token validation response: %w", err)
	}

	if !tokenResp.Data.IsValid {
		return nil, fmt.Errorf("access token is invalid or expired")
	}

	return &tokenResp.Data, nil
}

// getPhoneNumberDetails retrieves details about the phone number
func (c *Client) getPhoneNumberDetails(ctx context.Context) (*PhoneNumberDetails, error) {
	url := fmt.Sprintf("https://graph.facebook.com/%s/%s",
		c.config.APIVersion,
		c.config.PhoneNumberID)

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create phone number details request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to get phone number details: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read phone number details response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		var errResp ErrorResponse
		if err := json.Unmarshal(body, &errResp); err == nil {
			return nil, fmt.Errorf("API error: %s (code: %d, subcode: %d)",
				errResp.Error.Message, errResp.Error.Code, errResp.Error.ErrorSubcode)
		}
		return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
	}

	var phoneDetails PhoneNumberDetails
	if err := json.Unmarshal(body, &phoneDetails); err != nil {
		return nil, fmt.Errorf("failed to parse phone number details: %w", err)
	}

	return &phoneDetails, nil
}

// getBusinessAccountDetails retrieves details about the business account
func (c *Client) getBusinessAccountDetails(ctx context.Context) (*BusinessAccountDetails, error) {
	url := fmt.Sprintf("https://graph.facebook.com/%s/%s",
		c.config.APIVersion,
		c.config.BusinessAccountID)

	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to create business account details request: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to get business account details: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read business account details response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		var errResp ErrorResponse
		if err := json.Unmarshal(body, &errResp); err == nil {
			return nil, fmt.Errorf("API error: %s (code: %d)", errResp.Error.Message, errResp.Error.Code)
		}
		return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
	}

	var businessDetails BusinessAccountDetails
	if err := json.Unmarshal(body, &businessDetails); err != nil {
		return nil, fmt.Errorf("failed to parse business account details: %w", err)
	}

	return &businessDetails, nil
}

// checkMissingScopes checks which required scopes are missing from the token
func (c *Client) checkMissingScopes(currentScopes []string, requiredScopes []string) []string {
	scopeMap := make(map[string]bool)
	for _, scope := range currentScopes {
		scopeMap[scope] = true
	}

	var missing []string
	for _, required := range requiredScopes {
		if !scopeMap[required] {
			missing = append(missing, required)
		}
	}

	return missing
}

// fetchWABAID resolves the WhatsApp Business Account (WABA) ID by iterating the WABAs
// owned by the configured business account and matching against our phone number ID.
// Requires business_account_id to be set in config.
// GET /{business-id}/owned_whatsapp_business_accounts?fields=id,phone_numbers{id}
func (c *Client) fetchWABAID(ctx context.Context) (string, error) {
	if c.config.BusinessAccountID == "" {
		return "", fmt.Errorf("waba_id or business_account_id must be set in config to resolve WABA")
	}

	var result struct {
		Data []struct {
			ID           string `json:"id"`
			PhoneNumbers struct {
				Data []struct {
					ID string `json:"id"`
				} `json:"data"`
			} `json:"phone_numbers"`
		} `json:"data"`
	}

	params := url.Values{"fields": {"id,phone_numbers{id}"}}
	if err := c.graphAPIGet(ctx, c.config.BusinessAccountID+"/owned_whatsapp_business_accounts", params, &result); err != nil {
		return "", fmt.Errorf("failed to list WABAs for business %s: %w", c.config.BusinessAccountID, err)
	}

	for _, waba := range result.Data {
		for _, phone := range waba.PhoneNumbers.Data {
			if phone.ID == c.config.PhoneNumberID {
				return waba.ID, nil
			}
		}
	}

	return "", fmt.Errorf("phone number %s not found in any WABA owned by business %s", c.config.PhoneNumberID, c.config.BusinessAccountID)
}

// formatExpiry formats the expiry timestamp for logging
func (c *Client) formatExpiry(expiresAt int64) string {
	if expiresAt == 0 {
		return "never"
	}
	expiryTime := time.Unix(expiresAt, 0)
	return expiryTime.Format("2006-01-02 15:04:05 MST")
}
