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.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.Duration;
import java.util.Map;
import java.util.HashMap;

/**
 * BotExecution aggregate representing individual bot execution instances
 */
public class BotExecution extends AggregateRoot<BotExecutionId> {
    
    private CompanyId companyId;
    private BotId botId;
    private SocialAccountId socialAccountId;
    private ExecutionStatus status;
    private LocalDateTime startedAt;
    private LocalDateTime completedAt;
    private int actionsPerformed;
    private int actionsSuccessful;
    private int actionsFailed;
    private String errorMessage;
    private Map<String, Object> executionMetadata;
    private Map<String, Integer> actionCounts;
    private Duration totalDuration;
    
    // Constructor for new execution
    public BotExecution(CompanyId companyId, BotId botId, SocialAccountId socialAccountId) {
        super(BotExecutionId.generate());
        this.companyId = validateCompanyId(companyId);
        this.botId = validateBotId(botId);
        this.socialAccountId = socialAccountId;
        this.status = ExecutionStatus.PENDING;
        this.actionsPerformed = 0;
        this.actionsSuccessful = 0;
        this.actionsFailed = 0;
        this.executionMetadata = new HashMap<>();
        this.actionCounts = new HashMap<>();
    }
    
    // Constructor for existing execution
    public BotExecution(BotExecutionId id, CompanyId companyId, BotId botId, SocialAccountId socialAccountId,
                        ExecutionStatus status, LocalDateTime startedAt, LocalDateTime completedAt,
                        int actionsPerformed, int actionsSuccessful, int actionsFailed,
                        String errorMessage, Map<String, Object> executionMetadata,
                        Map<String, Integer> actionCounts, Duration totalDuration) {
        super(id);
        this.companyId = companyId;
        this.botId = botId;
        this.socialAccountId = socialAccountId;
        this.status = status;
        this.startedAt = startedAt;
        this.completedAt = completedAt;
        this.actionsPerformed = actionsPerformed;
        this.actionsSuccessful = actionsSuccessful;
        this.actionsFailed = actionsFailed;
        this.errorMessage = errorMessage;
        this.executionMetadata = executionMetadata != null ? new HashMap<>(executionMetadata) : new HashMap<>();
        this.actionCounts = actionCounts != null ? new HashMap<>(actionCounts) : new HashMap<>();
        this.totalDuration = totalDuration;
    }
    
    // Business methods
    public void start() {
        if (this.status != ExecutionStatus.PENDING) {
            throw new BusinessRuleViolationException("Execution can only be started from PENDING status");
        }
        this.status = ExecutionStatus.RUNNING;
        this.startedAt = LocalDateTime.now();
    }
    
    public void complete() {
        if (!this.status.isActive()) {
            throw new BusinessRuleViolationException("Only active executions can be completed");
        }
        this.status = ExecutionStatus.COMPLETED;
        this.completedAt = LocalDateTime.now();
        this.calculateDuration();
    }
    
    public void fail(String errorMessage) {
        if (this.status.isTerminal()) {
            throw new BusinessRuleViolationException("Cannot fail a terminal execution");
        }
        this.status = ExecutionStatus.FAILED;
        this.errorMessage = errorMessage;
        this.completedAt = LocalDateTime.now();
        this.calculateDuration();
    }
    
    public void cancel() {
        if (!this.status.canBeCancelled()) {
            throw new BusinessRuleViolationException("Execution cannot be cancelled in current status: " + this.status);
        }
        this.status = ExecutionStatus.CANCELLED;
        this.completedAt = LocalDateTime.now();
        this.calculateDuration();
    }
    
    public void pause() {
        if (!this.status.canBePaused()) {
            throw new BusinessRuleViolationException("Execution cannot be paused in current status: " + this.status);
        }
        this.status = ExecutionStatus.PAUSED;
    }
    
    public void resume() {
        if (!this.status.canBeResumed()) {
            throw new BusinessRuleViolationException("Execution cannot be resumed in current status: " + this.status);
        }
        this.status = ExecutionStatus.RUNNING;
    }
    
    public void retry() {
        if (!this.status.canBeRetried()) {
            throw new BusinessRuleViolationException("Execution cannot be retried in current status: " + this.status);
        }
        this.status = ExecutionStatus.RETRYING;
        this.errorMessage = null;
        // Reset counters for retry
        this.actionsPerformed = 0;
        this.actionsSuccessful = 0;
        this.actionsFailed = 0;
        this.actionCounts.clear();
    }
    
    public void recordAction(String actionType, boolean successful) {
        if (!this.status.isInProgress()) {
            throw new BusinessRuleViolationException("Cannot record actions on non-active execution");
        }
        
        this.actionsPerformed++;
        if (successful) {
            this.actionsSuccessful++;
        } else {
            this.actionsFailed++;
        }
        
        // Track action type counts
        this.actionCounts.merge(actionType, 1, Integer::sum);
    }
    
    public void addMetadata(String key, Object value) {
        if (key == null || key.trim().isEmpty()) {
            throw new BusinessRuleViolationException("Metadata key cannot be empty");
        }
        this.executionMetadata.put(key, value);
    }
    
    public void removeMetadata(String key) {
        this.executionMetadata.remove(key);
    }
    
    public void updateMetadata(Map<String, Object> metadata) {
        this.executionMetadata = metadata != null ? new HashMap<>(metadata) : new HashMap<>();
    }
    
    // Query methods
    public boolean isRunning() {
        return this.status.isInProgress();
    }
    
    public boolean isCompleted() {
        return this.status.isSuccessful();
    }
    
    public boolean isFailed() {
        return this.status.hasError();
    }
    
    public boolean isTerminal() {
        return this.status.isTerminal();
    }
    
    public boolean belongsToCompany(CompanyId companyId) {
        return this.companyId.equals(companyId);
    }
    
    public boolean belongsToBot(BotId botId) {
        return this.botId.equals(botId);
    }
    
    public boolean usedSocialAccount(SocialAccountId socialAccountId) {
        return this.socialAccountId != null && this.socialAccountId.equals(socialAccountId);
    }
    
    public double getSuccessRate() {
        if (this.actionsPerformed == 0) {
            return 0.0;
        }
        return (double) this.actionsSuccessful / this.actionsPerformed * 100.0;
    }
    
    public double getFailureRate() {
        if (this.actionsPerformed == 0) {
            return 0.0;
        }
        return (double) this.actionsFailed / this.actionsPerformed * 100.0;
    }
    
    public int getActionCount(String actionType) {
        return this.actionCounts.getOrDefault(actionType, 0);
    }
    
    public boolean hasMetadata(String key) {
        return this.executionMetadata.containsKey(key);
    }
    
    public Object getMetadata(String key) {
        return this.executionMetadata.get(key);
    }
    
    public String getStringMetadata(String key, String defaultValue) {
        Object value = this.executionMetadata.get(key);
        return value instanceof String ? (String) value : defaultValue;
    }
    
    public Integer getIntegerMetadata(String key, Integer defaultValue) {
        Object value = this.executionMetadata.get(key);
        return value instanceof Integer ? (Integer) value : defaultValue;
    }
    
    public long getDurationInMinutes() {
        if (this.totalDuration != null) {
            return this.totalDuration.toMinutes();
        }
        if (this.startedAt != null && this.completedAt != null) {
            return Duration.between(this.startedAt, this.completedAt).toMinutes();
        }
        return 0;
    }
    
    public long getDurationInSeconds() {
        if (this.totalDuration != null) {
            return this.totalDuration.getSeconds();
        }
        if (this.startedAt != null && this.completedAt != null) {
            return Duration.between(this.startedAt, this.completedAt).getSeconds();
        }
        return 0;
    }
    
    // Private methods
    private void calculateDuration() {
        if (this.startedAt != null && this.completedAt != null) {
            this.totalDuration = Duration.between(this.startedAt, this.completedAt);
        }
    }
    
    // Validation methods
    private CompanyId validateCompanyId(CompanyId companyId) {
        if (companyId == null) {
            throw new BusinessRuleViolationException("Company ID is required");
        }
        return companyId;
    }
    
    private BotId validateBotId(BotId botId) {
        if (botId == null) {
            throw new BusinessRuleViolationException("Bot ID is required");
        }
        return botId;
    }
    
    // Getters
    public CompanyId getCompanyId() { return companyId; }
    public BotId getBotId() { return botId; }
    public SocialAccountId getSocialAccountId() { return socialAccountId; }
    public ExecutionStatus getStatus() { return status; }
    public LocalDateTime getStartedAt() { return startedAt; }
    public LocalDateTime getCompletedAt() { return completedAt; }
    public int getActionsPerformed() { return actionsPerformed; }
    public int getActionsSuccessful() { return actionsSuccessful; }
    public int getActionsFailed() { return actionsFailed; }
    public String getErrorMessage() { return errorMessage; }
    public Map<String, Object> getExecutionMetadata() { return new HashMap<>(executionMetadata); }
    public Map<String, Integer> getActionCounts() { return new HashMap<>(actionCounts); }
    public Duration getTotalDuration() { return totalDuration; }
}
