package com.social.media.domain.bot.aggregate;

import com.social.media.domain.bot.valueobject.*;
import com.social.media.domain.company.valueobject.CompanyId;
import com.social.media.domain.user.valueobject.UserId;
import com.social.media.domain.socialaccount.valueobject.SocialAccountId;
import com.social.media.domain.shared.exception.BusinessRuleViolationException;
import com.social.media.domain.shared.kernel.AggregateRoot;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Set;
import java.util.HashSet;

/**
 * Bot aggregate representing automated social media bots
 */
public class Bot extends AggregateRoot<BotId> {
    
    private CompanyId companyId;
    private UserId createdBy;
    private String name;
    private String description;
    private BotType botType;
    private BotStatus status;
    private BotConfiguration configuration;
    private Set<SocialAccountId> targetSocialAccounts;
    private Set<UserListId> targetUserLists;
    private LocalDateTime lastExecutionAt;
    private LocalDateTime nextExecutionAt;
    private int totalExecutions;
    private int successfulExecutions;
    private int failedExecutions;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // Constructor for new bot
    public Bot(CompanyId companyId, UserId createdBy, String name, String description,
               BotType botType, BotConfiguration configuration) {
        super(BotId.generate());
        this.companyId = validateCompanyId(companyId);
        this.createdBy = validateCreatedBy(createdBy);
        this.name = validateName(name);
        this.description = description;
        this.botType = validateBotType(botType);
        this.status = BotStatus.INACTIVE;
        this.configuration = configuration != null ? configuration : BotConfiguration.defaultConfiguration();
        this.targetSocialAccounts = new HashSet<>();
        this.targetUserLists = new HashSet<>();
        this.totalExecutions = 0;
        this.successfulExecutions = 0;
        this.failedExecutions = 0;
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    // Constructor for existing bot
    public Bot(BotId id, CompanyId companyId, UserId createdBy, String name, String description,
               BotType botType, BotStatus status, BotConfiguration configuration,
               Set<SocialAccountId> targetSocialAccounts, Set<UserListId> targetUserLists,
               LocalDateTime lastExecutionAt, LocalDateTime nextExecutionAt,
               int totalExecutions, int successfulExecutions, int failedExecutions,
               LocalDateTime createdAt, LocalDateTime updatedAt) {
        super(id);
        this.companyId = companyId;
        this.createdBy = createdBy;
        this.name = name;
        this.description = description;
        this.botType = botType;
        this.status = status;
        this.configuration = configuration;
        this.targetSocialAccounts = targetSocialAccounts != null ? new HashSet<>(targetSocialAccounts) : new HashSet<>();
        this.targetUserLists = targetUserLists != null ? new HashSet<>(targetUserLists) : new HashSet<>();
        this.lastExecutionAt = lastExecutionAt;
        this.nextExecutionAt = nextExecutionAt;
        this.totalExecutions = totalExecutions;
        this.successfulExecutions = successfulExecutions;
        this.failedExecutions = failedExecutions;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }
    
    // Business methods
    public void updateInfo(String name, String description) {
        if (!this.status.canBeEdited()) {
            throw new BusinessRuleViolationException("Bot cannot be edited in current status: " + this.status);
        }
        this.name = validateName(name);
        this.description = description;
        this.updatedAt = LocalDateTime.now();
    }
    
    public void updateConfiguration(BotConfiguration configuration) {
        if (!this.status.canBeEdited()) {
            throw new BusinessRuleViolationException("Bot configuration cannot be updated in current status: " + this.status);
        }
        this.configuration = configuration != null ? configuration : BotConfiguration.defaultConfiguration();
        this.updatedAt = LocalDateTime.now();
    }
    
    public void activate() {
        if (!this.status.canBeActivated()) {
            throw new BusinessRuleViolationException("Bot cannot be activated from status: " + this.status);
        }
        validateBotReadiness();
        this.status = BotStatus.ACTIVE;
        this.updatedAt = LocalDateTime.now();
    }
    
    public void deactivate() {
        if (!this.status.canBeDeactivated()) {
            throw new BusinessRuleViolationException("Bot cannot be deactivated from status: " + this.status);
        }
        this.status = BotStatus.INACTIVE;
        this.nextExecutionAt = null;
        this.updatedAt = LocalDateTime.now();
    }
    
    public void start() {
        if (!this.status.canBeStarted()) {
            throw new BusinessRuleViolationException("Bot cannot be started from status: " + this.status);
        }
        validateBotReadiness();
        this.status = BotStatus.RUNNING;
        this.lastExecutionAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    public void pause() {
        if (!this.status.canBePaused()) {
            throw new BusinessRuleViolationException("Bot cannot be paused from status: " + this.status);
        }
        this.status = BotStatus.PAUSED;
        this.updatedAt = LocalDateTime.now();
    }
    
    public void stop() {
        if (!this.status.canBeStopped()) {
            throw new BusinessRuleViolationException("Bot cannot be stopped from status: " + this.status);
        }
        this.status = BotStatus.ACTIVE;
        this.updatedAt = LocalDateTime.now();
    }
    
    public void markAsError(String errorMessage) {
        this.status = BotStatus.ERROR;
        this.failedExecutions++;
        this.updatedAt = LocalDateTime.now();
        // Could store error message in configuration or separate field
    }
    
    public void markAsCompleted() {
        this.status = BotStatus.COMPLETED;
        this.successfulExecutions++;
        this.updatedAt = LocalDateTime.now();
    }
    
    public void addTargetSocialAccount(SocialAccountId socialAccountId) {
        if (socialAccountId == null) {
            throw new BusinessRuleViolationException("Social account ID cannot be null");
        }
        this.targetSocialAccounts.add(socialAccountId);
        this.updatedAt = LocalDateTime.now();
    }
    
    public void removeTargetSocialAccount(SocialAccountId socialAccountId) {
        this.targetSocialAccounts.remove(socialAccountId);
        this.updatedAt = LocalDateTime.now();
    }
    
    public void clearTargetSocialAccounts() {
        this.targetSocialAccounts.clear();
        this.updatedAt = LocalDateTime.now();
    }
    
    public void addTargetUserList(UserListId userListId) {
        if (userListId == null) {
            throw new BusinessRuleViolationException("User list ID cannot be null");
        }
        this.targetUserLists.add(userListId);
        this.updatedAt = LocalDateTime.now();
    }
    
    public void removeTargetUserList(UserListId userListId) {
        this.targetUserLists.remove(userListId);
        this.updatedAt = LocalDateTime.now();
    }
    
    public void clearTargetUserLists() {
        this.targetUserLists.clear();
        this.updatedAt = LocalDateTime.now();
    }
    
    public void scheduleNextExecution(LocalDateTime nextExecution) {
        if (nextExecution != null && nextExecution.isBefore(LocalDateTime.now())) {
            throw new BusinessRuleViolationException("Cannot schedule execution for past time");
        }
        this.nextExecutionAt = nextExecution;
        this.updatedAt = LocalDateTime.now();
    }
    
    public void recordExecution(boolean successful) {
        this.totalExecutions++;
        if (successful) {
            this.successfulExecutions++;
        } else {
            this.failedExecutions++;
        }
        this.lastExecutionAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    // Query methods
    public boolean isActive() {
        return this.status == BotStatus.ACTIVE;
    }
    
    public boolean isRunning() {
        return this.status == BotStatus.RUNNING;
    }
    
    public boolean isPaused() {
        return this.status == BotStatus.PAUSED;
    }
    
    public boolean hasError() {
        return this.status.hasError();
    }
    
    public boolean isReadyToRun() {
        return this.status.canExecute() && hasTargetAccounts() && isWithinOperatingHours();
    }
    
    public boolean hasTargetAccounts() {
        return !this.targetSocialAccounts.isEmpty();
    }
    
    public boolean hasTargetUserLists() {
        return !this.targetUserLists.isEmpty();
    }
    
    public boolean belongsToCompany(CompanyId companyId) {
        return this.companyId.equals(companyId);
    }
    
    public boolean wasCreatedBy(UserId userId) {
        return this.createdBy.equals(userId);
    }
    
    public boolean isScheduledForExecution() {
        return this.nextExecutionAt != null && this.nextExecutionAt.isBefore(LocalDateTime.now().plusMinutes(5));
    }
    
    public boolean isWithinOperatingHours() {
        return this.configuration.isWithinOperatingHours(LocalTime.now());
    }
    
    public double getSuccessRate() {
        if (this.totalExecutions == 0) {
            return 0.0;
        }
        return (double) this.successfulExecutions / this.totalExecutions * 100.0;
    }
    
    public double getFailureRate() {
        if (this.totalExecutions == 0) {
            return 0.0;
        }
        return (double) this.failedExecutions / this.totalExecutions * 100.0;
    }
    
    // Validation methods
    private CompanyId validateCompanyId(CompanyId companyId) {
        if (companyId == null) {
            throw new BusinessRuleViolationException("Company ID is required");
        }
        return companyId;
    }
    
    private UserId validateCreatedBy(UserId createdBy) {
        if (createdBy == null) {
            throw new BusinessRuleViolationException("Created by user ID is required");
        }
        return createdBy;
    }
    
    private String validateName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new BusinessRuleViolationException("Bot name is required");
        }
        if (name.length() > 100) {
            throw new BusinessRuleViolationException("Bot name must not exceed 100 characters");
        }
        return name.trim();
    }
    
    private BotType validateBotType(BotType botType) {
        if (botType == null) {
            throw new BusinessRuleViolationException("Bot type is required");
        }
        return botType;
    }
    
    private void validateBotReadiness() {
        if (this.botType.requiresTargetAccounts() && this.targetSocialAccounts.isEmpty()) {
            throw new BusinessRuleViolationException("Bot requires target social accounts to be configured");
        }
        // Add more validation based on bot type requirements
    }
    
    // Getters
    public CompanyId getCompanyId() { return companyId; }
    public UserId getCreatedBy() { return createdBy; }
    public String getName() { return name; }
    public String getDescription() { return description; }
    public BotType getBotType() { return botType; }
    public BotStatus getStatus() { return status; }
    public BotConfiguration getConfiguration() { return configuration; }
    public Set<SocialAccountId> getTargetSocialAccounts() { return new HashSet<>(targetSocialAccounts); }
    public Set<UserListId> getTargetUserLists() { return new HashSet<>(targetUserLists); }
    public LocalDateTime getLastExecutionAt() { return lastExecutionAt; }
    public LocalDateTime getNextExecutionAt() { return nextExecutionAt; }
    public int getTotalExecutions() { return totalExecutions; }
    public int getSuccessfulExecutions() { return successfulExecutions; }
    public int getFailedExecutions() { return failedExecutions; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public LocalDateTime getUpdatedAt() { return updatedAt; }
}
