package inspection

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

	"dc-inspection/internal/config"
	"dc-inspection/pkg/ldap"
)

// ADSecurityInspector AD 域账号安全扫描器
type ADSecurityInspector struct {
	cfg *config.LDAPConfig
	ad  *config.ADSecurityConfig
}

// NewADSecurityInspector 创建 AD 安全扫描器
func NewADSecurityInspector(ldapCfg *config.LDAPConfig, adCfg *config.ADSecurityConfig) *ADSecurityInspector {
	return &ADSecurityInspector{
		cfg: ldapCfg,
		ad:  adCfg,
	}
}

// SecurityReport AD 安全扫描报告
type SecurityReport struct {
	TotalAccounts      int               `json:"total_accounts"`
	DisabledAccounts   int               `json:"disabled_accounts"`
	LockedAccounts     int               `json:"locked_accounts"`
	ExpiredPasswords   int               `json:"expired_passwords"`
	NeverExpirePasswords int             `json:"never_expire_passwords"`
	InactiveAccounts   []InactiveAccount `json:"inactive_accounts"`
	RiskAccounts       []RiskAccount     `json:"risk_accounts"`
	PrivilegedUsers    []PrivilegedUser  `json:"privileged_users"`
	ScannedAt          time.Time         `json:"scanned_at"`
	Errors             []string          `json:"errors,omitempty"`
}

// InactiveAccount 长期未登录账户
type InactiveAccount struct {
	Username    string    `json:"username"`
	DisplayName string    `json:"display_name"`
	LastLogon   time.Time `json:"last_logon"`
	DN          string    `json:"dn"`
}

// RiskAccount 风险账户
type RiskAccount struct {
	Username    string    `json:"username"`
	DisplayName string    `json:"display_name"`
	RiskType    string    `json:"risk_type"` // locked / password_expired / inactive / never_expire
	RiskLevel   string    `json:"risk_level"` // high / medium / low
	Description string    `json:"description"`
	DN          string    `json:"dn"`
}

// PrivilegedUser 特权用户
type PrivilegedUser struct {
	Username    string   `json:"username"`
	DisplayName string   `json:"display_name"`
	Groups      []string `json:"groups"`
	DN          string   `json:"dn"`
}

// Inspect 执行 AD 域安全扫描
func (a *ADSecurityInspector) Inspect() *SecurityReport {
	report := &SecurityReport{
		ScannedAt: time.Now(),
	}

	// 连接 AD 域
	clientCfg := ldap.ClientConfig{
		Host:       a.cfg.Host,
		Port:       a.cfg.Port,
		BindDN:     a.cfg.BindDN,
		Password:   a.cfg.Password,
		BaseDN:     a.cfg.BaseDN,
		UseTLS:     a.cfg.UseTLS,
		SkipVerify: a.cfg.SkipVerify,
		Timeout:    30 * time.Second,
	}

	client, err := ldap.NewClient(clientCfg)
	if err != nil {
		report.Errors = append(report.Errors, fmt.Sprintf("AD 域连接失败: %v", err))
		return report
	}
	defer client.Close()

	// 搜索所有用户
	users, err := client.SearchUsers(a.cfg.BaseDN, "")
	if err != nil {
		report.Errors = append(report.Errors, fmt.Sprintf("AD 用户搜索失败: %v", err))
		return report
	}

	report.TotalAccounts = len(users)

	// 逐用户分析安全风险
	now := time.Now()
	inactiveThreshold := 30 * 24 * time.Hour // 30天未登录视为不活跃

	for _, user := range users {
		// 统计禁用账户
		if !user.Enabled {
			report.DisabledAccounts++
		}

		// 检查锁定账户
		if user.Locked {
			report.LockedAccounts++
			if a.ad.CheckLockedAccounts {
				report.RiskAccounts = append(report.RiskAccounts, RiskAccount{
					Username:    user.Username,
					DisplayName: user.DisplayName,
					RiskType:    "locked",
					RiskLevel:   "high",
					Description: "账户被锁定，可能存在暴力破解攻击",
					DN:          user.DN,
				})
			}
		}

		// 检查密码过期
		if user.PasswordExpired {
			report.ExpiredPasswords++
			report.RiskAccounts = append(report.RiskAccounts, RiskAccount{
				Username:    user.Username,
				DisplayName: user.DisplayName,
				RiskType:    "password_expired",
				RiskLevel:   "medium",
				Description: "密码已过期",
				DN:          user.DN,
			})
		}

		// 检查密码永不过期
		if user.PasswordNeverExpires && a.ad.CheckPasswordNeverExpires {
			report.NeverExpirePasswords++
			report.RiskAccounts = append(report.RiskAccounts, RiskAccount{
				Username:    user.Username,
				DisplayName: user.DisplayName,
				RiskType:    "never_expire",
				RiskLevel:   "medium",
				Description: "密码设置为永不过期，不符合安全策略",
				DN:          user.DN,
			})
		}

		// 检查长期未登录
		if !user.LastLogon.IsZero() {
			inactiveDuration := now.Sub(user.LastLogon)
			if inactiveDuration > inactiveThreshold && user.Enabled {
				report.InactiveAccounts = append(report.InactiveAccounts, InactiveAccount{
					Username:    user.Username,
					DisplayName: user.DisplayName,
					LastLogon:   user.LastLogon,
					DN:          user.DN,
				})
				report.RiskAccounts = append(report.RiskAccounts, RiskAccount{
					Username:    user.Username,
					DisplayName: user.DisplayName,
					RiskType:    "inactive",
					RiskLevel:   "low",
					Description: fmt.Sprintf("已 %d 天未登录", int(inactiveDuration.Hours()/24)),
					DN:          user.DN,
				})
			}
		}

		// 检查密码最大使用天数
		if a.ad.PasswordMaxAge > 0 && !user.PwdLastSet.IsZero() && user.Enabled {
			pwdAge := now.Sub(user.PwdLastSet)
			if int(pwdAge.Hours()/24) > a.ad.PasswordMaxAge {
				report.RiskAccounts = append(report.RiskAccounts, RiskAccount{
					Username:    user.Username,
					DisplayName: user.DisplayName,
					RiskType:    "password_age_exceeded",
					RiskLevel:   "medium",
					Description: fmt.Sprintf("密码已使用 %d 天，超过最大期限 %d 天",
						int(pwdAge.Hours()/24), a.ad.PasswordMaxAge),
					DN: user.DN,
				})
			}
		}
	}

	// 检查特权组成员
	for _, groupDN := range a.ad.PrivilegedGroups {
		members, err := client.SearchGroupMembers(a.cfg.BaseDN, groupDN)
		if err != nil {
			report.Errors = append(report.Errors, fmt.Sprintf("查询特权组 %s 成员失败: %v", groupDN, err))
			continue
		}

		groupName := extractGroupName(groupDN)
		for _, member := range members {
			// 检查是否已存在
			found := false
			for i := range report.PrivilegedUsers {
				if report.PrivilegedUsers[i].Username == member.Username {
					report.PrivilegedUsers[i].Groups = append(report.PrivilegedUsers[i].Groups, groupName)
					found = true
					break
				}
			}
			if !found {
				report.PrivilegedUsers = append(report.PrivilegedUsers, PrivilegedUser{
					Username:    member.Username,
					DisplayName: member.DisplayName,
					Groups:      []string{groupName},
					DN:          member.DN,
				})
			}
		}
	}

	return report
}

// CheckThresholds 检查安全阈值
func (a *ADSecurityInspector) CheckThresholds(report *SecurityReport) []string {
	var alerts []string

	// 锁定账户告警
	if report.LockedAccounts > 0 {
		alerts = append(alerts, fmt.Sprintf("检测到 %d 个被锁定账户，可能存在暴力破解攻击", report.LockedAccounts))
	}

	// 过期密码告警
	if report.ExpiredPasswords > 0 {
		alerts = append(alerts, fmt.Sprintf("检测到 %d 个密码已过期账户", report.ExpiredPasswords))
	}

	// 密码永不过期告警
	if report.NeverExpirePasswords > 0 {
		alerts = append(alerts, fmt.Sprintf("检测到 %d 个密码永不过期账户，不符合安全策略", report.NeverExpirePasswords))
	}

	// 不活跃账户告警
	if len(report.InactiveAccounts) > 0 {
		alerts = append(alerts, fmt.Sprintf("检测到 %d 个长期未登录账户（>30天）", len(report.InactiveAccounts)))
	}

	// 高风险账户告警
	highRiskCount := 0
	for _, risk := range report.RiskAccounts {
		if risk.RiskLevel == "high" {
			highRiskCount++
		}
	}
	if highRiskCount > 0 {
		alerts = append(alerts, fmt.Sprintf("检测到 %d 个高风险账户需要立即处理", highRiskCount))
	}

	// 连接错误
	if len(report.Errors) > 0 {
		alerts = append(alerts, fmt.Sprintf("AD 扫描异常: %s", strings.Join(report.Errors, "; ")))
	}

	return alerts
}

// ToRecord 将扫描报告转换为数据库记录
func (a *ADSecurityInspector) ToRecord(report *SecurityReport) *Record {
	status := "ok"
	alerts := a.CheckThresholds(report)

	if len(report.Errors) > 0 {
		status = "error"
	} else if len(alerts) > 0 {
		// 根据风险等级确定状态
		for _, risk := range report.RiskAccounts {
			if risk.RiskLevel == "high" {
				status = "critical"
				break
			}
		}
		if status != "critical" {
			status = "warning"
		}
	}

	metricsJSON, _ := json.Marshal(report)

	details := fmt.Sprintf("总账户: %d, 禁用: %d, 锁定: %d, 密码过期: %d, 密码永不过期: %d, 不活跃: %d, 风险账户: %d, 特权用户: %d",
		report.TotalAccounts, report.DisabledAccounts, report.LockedAccounts,
		report.ExpiredPasswords, report.NeverExpirePasswords,
		len(report.InactiveAccounts), len(report.RiskAccounts), len(report.PrivilegedUsers))

	return &Record{
		InspectionType: "ad_security",
		Target:         a.cfg.BaseDN,
		Status:         status,
		Metrics:        string(metricsJSON),
		Details:        details,
		Message:        strings.Join(alerts, "\n"),
		InspectedAt:    time.Now(),
	}
}

// extractGroupName 从 DN 中提取组名
// 如 CN=Domain Admins,CN=Users,DC=example,DC=com -> Domain Admins
func extractGroupName(dn string) string {
	parts := strings.SplitN(dn, ",", 2)
	if len(parts) > 0 {
		eq := strings.SplitN(parts[0], "=", 2)
		if len(eq) > 1 {
			return eq[1]
		}
	}
	return dn
}
