// Package feishu provides a client for interacting with the Feishu (Lark) API.
// This file contains the approval data parser that uses JSON struct parsing
// instead of regex to parse Feishu approval instance responses.
package feishu

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"AutoSSO/internal/config"
	"AutoSSO/internal/pkg/logger"
)

// ApprovalData represents the parsed approval data from a Feishu approval instance.
// This struct contains all the relevant information extracted from the approval form.
//
// Requirements: 7.6 (JSON struct parsing instead of regex)
type ApprovalData struct {
	Email          string    // Applicant's email address
	AppType        bool      // Whether this is an SSO application type
	AccountID      string    // Cloud account ID
	PermissionSets []string  // List of permission sets requested
	StartTime      time.Time // Permission start time
	EndTime        time.Time // Permission end time
	IsApproved     bool      // Whether the approval is approved
	IsExpired      bool      // Whether the permission has expired
	CloudType      string    // Cloud platform type (AWS, GCP, Azure, Alibaba)
	DayNum         int       // Number of days for the permission
	Description    string    // Access resource description / Application reason
	MissingFields  []string  // List of required fields that were not found (for debugging)
}

// InstanceDetailResponse represents the full response from the Feishu instance detail API.
type InstanceDetailResponse struct {
	Code int                `json:"code"`
	Msg  string             `json:"msg"`
	Data InstanceDetailData `json:"data"`
}

// InstanceDetailData represents the data field in the instance detail response.
type InstanceDetailData struct {
	InstanceCode string `json:"instance_code"`
	Status       string `json:"status"` // PENDING, APPROVED, REJECTED, CANCELED, DELETED
	Form         string `json:"form"`   // JSON string containing form fields
	StartTime    string `json:"start_time"`
	EndTime      string `json:"end_time"`
}

// FormField represents a single field in the approval form.
type FormField struct {
	ID    string          `json:"id"`
	Name  string          `json:"name"`
	Type  string          `json:"type"`
	Value json.RawMessage `json:"value"` // Can be string, array, or object
	Ext   json.RawMessage `json:"ext,omitempty"`
}

// DateRangeValue represents a date range value in the form.
type DateRangeValue struct {
	Start string `json:"start"`
	End   string `json:"end"`
}

// Parser handles parsing of Feishu approval instance responses.
type Parser struct {
	logger        *logger.Logger
	fieldMappings *config.FeishuFieldMappings
}

// NewParser creates a new Parser instance.
func NewParser() *Parser {
	return &Parser{}
}

// WithLogger sets the logger for the parser.
func (p *Parser) WithLogger(log *logger.Logger) *Parser {
	p.logger = log
	return p
}

// WithFieldMappings sets custom field mappings for the parser.
// If not set, default built-in patterns are used.
func (p *Parser) WithFieldMappings(mappings *config.FeishuFieldMappings) *Parser {
	p.fieldMappings = mappings
	return p
}

// ParseInstanceDetail parses the raw JSON response from GetInstanceDetail into ApprovalData.
//
// Parameters:
//   - rawJSON: The raw JSON response bytes from the Feishu API
//
// Returns:
//   - *ApprovalData: The parsed approval data
//   - error: Any parsing error
//
// Requirements: 7.6 (JSON struct parsing instead of regex)
func (p *Parser) ParseInstanceDetail(rawJSON []byte) (*ApprovalData, error) {
	var response InstanceDetailResponse
	if err := json.Unmarshal(rawJSON, &response); err != nil {
		return nil, fmt.Errorf("failed to unmarshal instance detail response: %w", err)
	}

	if response.Code != 0 {
		return nil, fmt.Errorf("API error: code=%d, msg=%s", response.Code, response.Msg)
	}

	return p.parseInstanceData(&response.Data)
}

// parseInstanceData parses the instance data into ApprovalData.
func (p *Parser) parseInstanceData(data *InstanceDetailData) (*ApprovalData, error) {
	approvalData := &ApprovalData{}

	// Parse approval status
	approvalData.IsApproved = data.Status == "APPROVED"

	// Parse form fields
	var formFields []FormField
	if err := json.Unmarshal([]byte(data.Form), &formFields); err != nil {
		return nil, fmt.Errorf("failed to unmarshal form fields: %w", err)
	}

	// Log form fields for debugging
	if p.logger != nil {
		p.logger.Info("Parsed Feishu approval form fields",
			logger.String("instance_code", data.InstanceCode),
			logger.String("raw_status", data.Status),
			logger.Bool("is_approved", approvalData.IsApproved),
			logger.Any("fields", formFields),
		)
	}

	// Detect cloud type first to determine which fields to parse
	cloudType := p.detectCloudType(formFields)
	approvalData.CloudType = cloudType

	// Parse each form field
	for _, field := range formFields {
		if err := p.parseFormField(approvalData, &field, cloudType); err != nil {
			// Log warning but continue parsing other fields
			continue
		}
	}

	// Calculate if permission is expired
	if !approvalData.EndTime.IsZero() {
		approvalData.IsExpired = time.Now().After(approvalData.EndTime)
	}

	// Calculate day number
	if !approvalData.StartTime.IsZero() && !approvalData.EndTime.IsZero() {
		duration := approvalData.EndTime.Sub(approvalData.StartTime)
		approvalData.DayNum = int(duration.Hours() / 24)
		if approvalData.DayNum == 0 {
			approvalData.DayNum = 1
		}
	}

	return approvalData, nil
}

// getDefaultCloudDetectionKeywords returns the default keywords for cloud type detection.
func (p *Parser) getDefaultCloudDetectionKeywords() map[string][]string {
	return map[string][]string{
		"gcp":     {"gcp account", "gcp project"},
		"azure":   {"azure account", "azure subscription"},
		"alibaba": {"alibaba account", "alibaba cloud"},
	}
}

// getDefaultSSOTypeKeywords returns the default SSO type detection keywords.
func (p *Parser) getDefaultSSOTypeKeywords() []string {
	return []string{"sso", "生产账号申请", "production account application"}
}

// getDefaultDateFormats returns the default date format strings.
func (p *Parser) getDefaultDateFormats() []string {
	return []string{
		"2006-01-02T15:04:05Z07:00",
		"2006-01-02 15:04:05",
		"2006-01-02",
		"2006/01/02 15:04:05",
		"2006/01/02",
		"2006-01-02T15:04:05.000Z",
		"2006-01-02T15:04:05",
		"01/02/2006",
		"02-Jan-2006",
		"Jan 2, 2006",
	}
}

// detectCloudType detects the cloud platform type from form fields.
func (p *Parser) detectCloudType(fields []FormField) string {
	// Get cloud detection keywords from config or defaults
	cloudKeywords := p.getDefaultCloudDetectionKeywords()
	if p.fieldMappings != nil && p.fieldMappings.CloudDetection != nil {
		cloudKeywords = p.fieldMappings.CloudDetection
	}

	// Check each field against cloud-specific keywords
	for _, field := range fields {
		name := strings.ToLower(field.Name)
		
		// Check GCP keywords
		if keywords, ok := cloudKeywords["gcp"]; ok {
			for _, kw := range keywords {
				if strings.Contains(name, strings.ToLower(kw)) {
					return "GCP"
				}
			}
		}
		
		// Check Azure keywords
		if keywords, ok := cloudKeywords["azure"]; ok {
			for _, kw := range keywords {
				if strings.Contains(name, strings.ToLower(kw)) {
					return "Azure"
				}
			}
		}
		
		// Check Alibaba keywords
		if keywords, ok := cloudKeywords["alibaba"]; ok {
			for _, kw := range keywords {
				if strings.Contains(name, strings.ToLower(kw)) {
					return "Alibaba"
				}
			}
		}
	}

	// Return configured default or AWS
	if p.fieldMappings != nil && p.fieldMappings.DefaultCloudType != "" {
		return p.fieldMappings.DefaultCloudType
	}
	return "AWS"
}

// parseFormField parses a single form field and updates the ApprovalData.
func (p *Parser) parseFormField(data *ApprovalData, field *FormField, cloudType string) error {
	fieldName := strings.ToLower(field.Name)

	// Parse email field
	if p.isEmailField(fieldName) {
		return p.parseEmailField(data, field)
	}

	// Parse application type field
	if p.isAppTypeField(fieldName) {
		return p.parseAppTypeField(data, field)
	}

	// Parse account field based on cloud type
	if p.isAccountField(fieldName, cloudType) {
		return p.parseAccountField(data, field, cloudType)
	}

	// Parse permission/role field based on cloud type
	if p.isPermissionField(fieldName, cloudType) {
		return p.parsePermissionField(data, field)
	}

	// Parse date range field
	if p.isDateRangeField(field.Type) {
		return p.parseDateRangeField(data, field)
	}

	// Parse separate start time field
	if p.isStartTimeField(fieldName) {
		return p.parseStartTimeField(data, field)
	}

	// Parse separate end time field
	if p.isEndTimeField(fieldName) {
		return p.parseEndTimeField(data, field)
	}

	// Parse description field
	if p.isDescriptionField(fieldName) {
		return p.parseDescriptionField(data, field)
	}

	return nil
}

// containsKeyword checks if fieldName contains any of the keywords (case-insensitive).
func (p *Parser) containsKeyword(fieldName string, keywords []string) bool {
	for _, keyword := range keywords {
		if strings.Contains(fieldName, strings.ToLower(keyword)) {
			return true
		}
	}
	return false
}

// getDefaultEmailKeywords returns the default email field keywords.
func (p *Parser) getDefaultEmailKeywords() []string {
	return []string{"申请人邮箱", "邮箱", "applicant email", "application email"}
}

// getDefaultAppTypeKeywords returns the default app type field keywords.
func (p *Parser) getDefaultAppTypeKeywords() []string {
	return []string{"申请类型", "申请人选择申请类型", "applicant selects application type"}
}

// getDefaultDescriptionKeywords returns the default description field keywords.
func (p *Parser) getDefaultDescriptionKeywords() []string {
	return []string{"申请说明", "access resource description", "reason"}
}

// getDefaultStartTimeKeywords returns the default start time field keywords.
func (p *Parser) getDefaultStartTimeKeywords() []string {
	return []string{"start time", "开始时间"}
}

// getDefaultEndTimeKeywords returns the default end time field keywords.
func (p *Parser) getDefaultEndTimeKeywords() []string {
	return []string{"end time", "结束时间"}
}

// isEmailField checks if the field is an email field.
func (p *Parser) isEmailField(fieldName string) bool {
	// Check custom mappings first
	if p.fieldMappings != nil && len(p.fieldMappings.Email) > 0 {
		return p.containsKeyword(fieldName, p.fieldMappings.Email)
	}
	// Fall back to defaults
	return p.containsKeyword(fieldName, p.getDefaultEmailKeywords())
}

// parseEmailField parses the email field value.
func (p *Parser) parseEmailField(data *ApprovalData, field *FormField) error {
	var email string
	if err := json.Unmarshal(field.Value, &email); err != nil {
		return fmt.Errorf("failed to parse email field: %w", err)
	}
	data.Email = strings.TrimSpace(email)
	return nil
}

// isAppTypeField checks if the field is an application type field.
func (p *Parser) isAppTypeField(fieldName string) bool {
	// Check custom mappings first
	if p.fieldMappings != nil && len(p.fieldMappings.AppType) > 0 {
		return p.containsKeyword(fieldName, p.fieldMappings.AppType)
	}
	// Fall back to defaults
	return p.containsKeyword(fieldName, p.getDefaultAppTypeKeywords())
}

// parseAppTypeField parses the application type field value.
func (p *Parser) parseAppTypeField(data *ApprovalData, field *FormField) error {
	var appType string
	if err := json.Unmarshal(field.Value, &appType); err != nil {
		return fmt.Errorf("failed to parse app type field: %w", err)
	}
	appType = strings.ToLower(appType)
	
	// Get SSO type keywords from config or defaults
	ssoKeywords := p.getDefaultSSOTypeKeywords()
	if p.fieldMappings != nil && len(p.fieldMappings.SSOTypeKeywords) > 0 {
		ssoKeywords = p.fieldMappings.SSOTypeKeywords
	}
	
	// Check if it's an SSO application type
	data.AppType = false
	for _, kw := range ssoKeywords {
		if strings.Contains(appType, strings.ToLower(kw)) {
			data.AppType = true
			break
		}
	}
	return nil
}

// getDefaultAccountKeywords returns the default account field keywords for a cloud type.
func (p *Parser) getDefaultAccountKeywords(cloudType string) []string {
	basic := []string{"生产账号选择", "production account selection"}
	switch cloudType {
	case "GCP":
		return append(basic, "gcp project id", "gcp account")
	case "Azure":
		return append(basic, "azure subscription", "azure account")
	case "Alibaba":
		return append(basic, "alibaba account", "alibaba cloud")
	default:
		return append(basic, "aws account", "account")
	}
}

// isAccountField checks if the field is an account selection field.
func (p *Parser) isAccountField(fieldName string, cloudType string) bool {
	// Check custom mappings first
	if p.fieldMappings != nil && p.fieldMappings.Account != nil {
		cloudKey := strings.ToLower(cloudType)
		if keywords, ok := p.fieldMappings.Account[cloudKey]; ok && len(keywords) > 0 {
			return p.containsKeyword(fieldName, keywords)
		}
		// Try "default" key for generic mappings
		if keywords, ok := p.fieldMappings.Account["default"]; ok && len(keywords) > 0 {
			return p.containsKeyword(fieldName, keywords)
		}
	}
	// Fall back to defaults
	return p.containsKeyword(fieldName, p.getDefaultAccountKeywords(cloudType))
}

// parseAccountField parses the account field value.
func (p *Parser) parseAccountField(data *ApprovalData, field *FormField, cloudType string) error {
	var accountValue string
	if err := json.Unmarshal(field.Value, &accountValue); err != nil {
		return fmt.Errorf("failed to parse account field: %w", err)
	}

	// Extract account ID from the value
	accountID := p.extractAccountID(accountValue, cloudType)
	if accountID != "" {
		data.AccountID = accountID
	}
	return nil
}

// extractAccountID extracts the account ID from the account field value.
// The value format is typically "Account Name (#123456789)" or "project-name"
func (p *Parser) extractAccountID(value string, cloudType string) string {
	value = strings.TrimSpace(value)

	// For GCP, the value is typically the project ID directly
	if cloudType == "GCP" {
		// If it contains parentheses, extract the ID
		if idx := strings.Index(value, "(#"); idx != -1 {
			endIdx := strings.Index(value[idx:], ")")
			if endIdx != -1 {
				return strings.TrimPrefix(value[idx:idx+endIdx], "(#")
			}
		}
		// Otherwise, return the value as-is (it's the project ID)
		return value
	}

	// For Azure, extract subscription name
	if cloudType == "Azure" {
		// If it contains parentheses, extract the ID
		if idx := strings.Index(value, "(#"); idx != -1 {
			endIdx := strings.Index(value[idx:], ")")
			if endIdx != -1 {
				return strings.TrimPrefix(value[idx:idx+endIdx], "(#")
			}
		}
		// Otherwise, return the value as-is (it's the subscription name)
		return value
	}

	// For Alibaba, extract the account ID from format like "Account Name (#123456789)"
	if cloudType == "Alibaba" {
		if idx := strings.Index(value, "#"); idx != -1 {
			endIdx := strings.Index(value[idx:], ")")
			if endIdx != -1 {
				return strings.TrimPrefix(value[idx:idx+endIdx], "#")
			}
			// If no closing parenthesis, take everything after #
			return strings.TrimPrefix(value[idx:], "#")
		}
	}

	// For AWS and others, extract the account ID from format like "Account Name (#123456789)"
	if idx := strings.Index(value, "(#"); idx != -1 {
		endIdx := strings.Index(value[idx:], ")")
		if endIdx != -1 {
			return strings.TrimPrefix(value[idx:idx+endIdx], "(#")
		}
	}

	// If no special format, return the value as-is
	return value
}

// getDefaultPermissionKeywords returns the default permission field keywords for a cloud type.
func (p *Parser) getDefaultPermissionKeywords(cloudType string) []string {
	basic := []string{"选择生产 sso role", "select the production sso role"}
	switch cloudType {
	case "GCP":
		return append(basic, "select the gcp production sso role", "gcp sso role", "gcp role")
	case "Azure":
		return append(basic, "select the azure production sso role", "azure sso role", "azure role")
	case "Alibaba":
		return append(basic, "select the alibaba production sso role", "alibaba sso role", "alibaba role", "alibaba cloud")
	default:
		return append(basic, "sso role", "permission")
	}
}

// isPermissionField checks if the field is a permission/role selection field.
func (p *Parser) isPermissionField(fieldName string, cloudType string) bool {
	// Check custom mappings first
	if p.fieldMappings != nil && p.fieldMappings.Permission != nil {
		cloudKey := strings.ToLower(cloudType)
		if keywords, ok := p.fieldMappings.Permission[cloudKey]; ok && len(keywords) > 0 {
			return p.containsKeyword(fieldName, keywords)
		}
		// Try "default" key for generic mappings
		if keywords, ok := p.fieldMappings.Permission["default"]; ok && len(keywords) > 0 {
			return p.containsKeyword(fieldName, keywords)
		}
	}
	// Fall back to defaults
	return p.containsKeyword(fieldName, p.getDefaultPermissionKeywords(cloudType))
}

// parsePermissionField parses the permission/role field value.
func (p *Parser) parsePermissionField(data *ApprovalData, field *FormField) error {
	// The value can be a single string or an array of strings
	var permissions []string

	// Try to parse as array first
	if err := json.Unmarshal(field.Value, &permissions); err != nil {
		// If that fails, try to parse as a single string
		var singlePermission string
		if err := json.Unmarshal(field.Value, &singlePermission); err != nil {
			return fmt.Errorf("failed to parse permission field: %w", err)
		}
		if singlePermission != "" {
			permissions = []string{singlePermission}
		}
	}

	// Clean up permission names
	cleanedPermissions := make([]string, 0, len(permissions))
	for _, perm := range permissions {
		perm = strings.TrimSpace(perm)
		if perm != "" {
			cleanedPermissions = append(cleanedPermissions, perm)
		}
	}

	data.PermissionSets = cleanedPermissions
	return nil
}

// isDateRangeField checks if the field is a date range field.
func (p *Parser) isDateRangeField(fieldType string) bool {
	return fieldType == "dateInterval" || fieldType == "date_interval"
}

// parseDateRangeField parses the date range field value.
func (p *Parser) parseDateRangeField(data *ApprovalData, field *FormField) error {
	var dateRange DateRangeValue
	if err := json.Unmarshal(field.Value, &dateRange); err != nil {
		return fmt.Errorf("failed to parse date range field: %w", err)
	}

	// Parse start time
	if dateRange.Start != "" {
		startTime, err := p.parseTime(dateRange.Start)
		if err != nil {
			return fmt.Errorf("failed to parse start time: %w", err)
		}
		data.StartTime = startTime
	}

	// Parse end time
	if dateRange.End != "" {
		endTime, err := p.parseTime(dateRange.End)
		if err != nil {
			return fmt.Errorf("failed to parse end time: %w", err)
		}
		data.EndTime = endTime
	}

	return nil
}

// parseTime parses a time string in various formats.
func (p *Parser) parseTime(timeStr string) (time.Time, error) {
	// Try RFC3339 format first (e.g., "2023-09-01T15:00:00+08:00")
	if t, err := time.Parse(time.RFC3339, timeStr); err == nil {
		return t, nil
	}

	// Get date formats from config or defaults
	formats := p.getDefaultDateFormats()
	if p.fieldMappings != nil && len(p.fieldMappings.DateFormats) > 0 {
		formats = p.fieldMappings.DateFormats
	}
	
	// Also try RFC1123 formats
	formats = append(formats, time.RFC1123, time.RFC1123Z)

	for _, format := range formats {
		if t, err := time.Parse(format, timeStr); err == nil {
			return t, nil
		}
	}

	if p.logger != nil {
		p.logger.Warn("Unable to parse time string",
			logger.String("time_str", timeStr),
		)
	}

	return time.Time{}, fmt.Errorf("unable to parse time: %s", timeStr)
}

// isStartTimeField checks if the field is a start time field.
func (p *Parser) isStartTimeField(fieldName string) bool {
	// Check custom mappings first
	if p.fieldMappings != nil && len(p.fieldMappings.StartTime) > 0 {
		return p.containsKeyword(fieldName, p.fieldMappings.StartTime)
	}
	// Fall back to defaults
	return p.containsKeyword(fieldName, p.getDefaultStartTimeKeywords())
}

// parseStartTimeField parses the start time field value.
func (p *Parser) parseStartTimeField(data *ApprovalData, field *FormField) error {
	var timeStr string
	if err := json.Unmarshal(field.Value, &timeStr); err != nil {
		return fmt.Errorf("failed to parse start time field: %w", err)
	}
	if timeStr == "" {
		return nil
	}
	t, err := p.parseTime(timeStr)
	if err != nil {
		return fmt.Errorf("failed to parse start time: %w", err)
	}
	data.StartTime = t
	return nil
}

// isEndTimeField checks if the field is an end time field.
func (p *Parser) isEndTimeField(fieldName string) bool {
	// Check custom mappings first
	if p.fieldMappings != nil && len(p.fieldMappings.EndTime) > 0 {
		return p.containsKeyword(fieldName, p.fieldMappings.EndTime)
	}
	// Fall back to defaults
	return p.containsKeyword(fieldName, p.getDefaultEndTimeKeywords())
}

// parseEndTimeField parses the end time field value.
func (p *Parser) parseEndTimeField(data *ApprovalData, field *FormField) error {
	var timeStr string
	if err := json.Unmarshal(field.Value, &timeStr); err != nil {
		return fmt.Errorf("failed to parse end time field: %w", err)
	}
	if timeStr == "" {
		return nil
	}
	t, err := p.parseTime(timeStr)
	if err != nil {
		return fmt.Errorf("failed to parse end time: %w", err)
	}
	data.EndTime = t
	return nil
}

// isDescriptionField checks if the field is a description field.
func (p *Parser) isDescriptionField(fieldName string) bool {
	// Check custom mappings first
	if p.fieldMappings != nil && len(p.fieldMappings.Description) > 0 {
		return p.containsKeyword(fieldName, p.fieldMappings.Description)
	}
	// Fall back to defaults
	return p.containsKeyword(fieldName, p.getDefaultDescriptionKeywords())
}

// parseDescriptionField parses the description field value.
func (p *Parser) parseDescriptionField(data *ApprovalData, field *FormField) error {
	var desc string
	if err := json.Unmarshal(field.Value, &desc); err != nil {
		return fmt.Errorf("failed to parse description field: %w", err)
	}
	data.Description = strings.TrimSpace(desc)
	return nil
}

// IsValidApprovalData checks if the parsed approval data is valid for processing.
// An approval is considered valid if it has email, is SSO type, has account ID,
// and has at least one permission set.
func (data *ApprovalData) IsValidApprovalData() bool {
	return data.Email != "" &&
		data.AppType &&
		data.AccountID != "" &&
		len(data.PermissionSets) > 0 &&
		data.PermissionSets[0] != ""
}

// IsQualifiedForProcessing checks if the approval data is qualified for permission processing.
// This includes being valid, approved, and not expired.
func (data *ApprovalData) IsQualifiedForProcessing() bool {
	return data.IsValidApprovalData() &&
		data.IsApproved &&
		!data.IsExpired
}

// String returns a string representation of the ApprovalData for logging.
func (data *ApprovalData) String() string {
	return fmt.Sprintf("ApprovalData{Email: %s, CloudType: %s, AccountID: %s, PermissionSets: %v, IsApproved: %v, IsExpired: %v, Description: %s}",
		data.Email, data.CloudType, data.AccountID, data.PermissionSets, data.IsApproved, data.IsExpired, data.Description)
}
