package com.social.media.domain.company;

import com.social.media.domain.shared.BaseEntity;
import com.social.media.domain.shared.enums.CompanyPlan;
import com.social.media.domain.shared.enums.CompanyStatus;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.UUID;

/**
 * Company entity representing a business organization in the Social Media Manager platform.
 * 
 * A Company is the main aggregate root that groups users, social accounts, and all related
 * business data. It represents a business entity with its own subscription plan, limits,
 * and configuration.
 * 
 * @author Social Media Manager Team
 * @version 2.0
 * @since 2025-01-01
 */
@Getter
@Setter(AccessLevel.PRIVATE)
public class Company extends BaseEntity {
    
    private String companyCode;
    private String name;
    private Cnpj cnpj;
    private String stateRegistration;
    private String phone;
    private Email email;
    private String website;
    private String activitySector;
    private String logoUrl;
    
    // Address (embedded value object)
    private Address address;
    
    // === HIERARCHY FIELDS ===
    
    /**
     * Parent company reference for hierarchy
     * NULL for matriz companies
     */
    private Company parentCompany;
    
    /**
     * Hierarchy level (0 = matriz, 1+ = filial levels)
     */
    private Integer hierarchyLevel = 0;
    
    /**
     * Branch type enumeration
     */
    private BranchType branchType = BranchType.MATRIZ;
    
    /**
     * Internal branch code for organization
     */
    private String branchCode;
    
    /**
     * Geographical region
     */
    private String region;
    
    /**
     * Branch-specific status
     */
    private BranchStatus branchStatus = BranchStatus.ACTIVE;
    
    // Business rules and limits
    private CompanyPlan activePlan;
    private int socialAccountsLimit;
    private int monthlyPostsLimit;
    private int usersLimit;
    
    // Status and lifecycle
    private CompanyStatus status;
    private Instant registrationDate;
    private Instant expirationDate;
    
    /**
     * Default constructor for JPA and frameworks.
     */
    protected Company() {
        super();
    }
    
    /**
     * Creates a new Company with required information.
     * 
     * @param id the company identifier
     * @param name the company name
     * @param email the company email
     * @param plan the initial subscription plan
     */
    public Company(UUID id, String name, String email, CompanyPlan plan) {
        super(id);
        
        validateNotEmpty(name, "Company name");
        validateNotNull(email, "Company email");
        validateNotNull(plan, "Company plan");
        
        this.name = name.trim();
        this.email = new Email(email);
        this.activePlan = plan;
        this.status = CompanyStatus.ATIVO;
        this.registrationDate = Instant.now();
        this.branchType = BranchType.MATRIZ;
        this.hierarchyLevel = 0;
        this.branchStatus = BranchStatus.ACTIVE;
        
        // Set default limits based on plan
        applyPlanLimits(plan);
        
        // Generate company code (will be set by infrastructure)
        this.companyCode = "COMP" + String.format("%06d", 0); // Placeholder
    }
    
    /**
     * Creates a new Company with full information.
     * 
     * @param id the company identifier
     * @param name the company name
     * @param cnpj the company CNPJ
     * @param email the company email
     * @param plan the subscription plan
     * @param address the company address
     */
    public Company(UUID id, String name, String cnpj, String email, 
                   CompanyPlan plan, Address address) {
        this(id, name, email, plan);
        
        if (cnpj != null && !cnpj.trim().isEmpty()) {
            this.cnpj = new Cnpj(cnpj);
        }
        
        this.address = address;
    }
    
    /**
     * Creates a new filial Company with parent reference.
     * 
     * @param id the company identifier
     * @param name the company name
     * @param email the company email
     * @param plan the subscription plan
     * @param parentCompany the parent company
     * @param branchType the branch type
     * @param branchCode the branch code
     * @param region the region
     */
    public Company(UUID id, String name, String email, CompanyPlan plan,
                   Company parentCompany, BranchType branchType, String branchCode, String region) {
        this(id, name, email, plan);
        
        this.parentCompany = parentCompany;
        this.branchType = branchType != null ? branchType : BranchType.FILIAL;
        this.hierarchyLevel = parentCompany != null ? parentCompany.getHierarchyLevel() + 1 : 0;
        this.branchCode = branchCode;
        this.region = region;
    }
    
    // Business methods
    
    // === HIERARCHY HELPER METHODS ===
    
    /**
     * Check if this company is a matriz (no parent)
     */
    public boolean isMatriz() {
        return parentCompany == null && branchType == BranchType.MATRIZ;
    }
    
    /**
     * Check if this company is a filial (has parent)
     */
    public boolean isFilial() {
        return parentCompany != null;
    }
    
    /**
     * Get the root company (matriz) in the hierarchy
     */
    public Company getRootCompany() {
        Company current = this;
        while (current.getParentCompany() != null) {
            current = current.getParentCompany();
        }
        return current;
    }
    
    /**
     * Check if this company is a direct child of another company
     */
    public boolean isDirectChildOf(Company company) {
        return parentCompany != null && parentCompany.getId().equals(company.getId());
    }
    
    /**
     * Check if this company is in the same hierarchy as another company
     */
    public boolean isInSameHierarchyAs(Company company) {
        return getRootCompany().getId().equals(company.getRootCompany().getId());
    }
    
    /**
     * Set parent company and update hierarchy level
     */
    public void setParentCompany(Company parent) {
        this.parentCompany = parent;
        this.hierarchyLevel = parent != null ? parent.getHierarchyLevel() + 1 : 0;
        if (parent != null && this.branchType == BranchType.MATRIZ) {
            this.branchType = BranchType.FILIAL;
        }
        touch();
    }
    
    /**
     * Change branch status
     */
    public void changeBranchStatus(BranchStatus newStatus) {
        validateNotNull(newStatus, "Branch status");
        this.branchStatus = newStatus;
        touch();
    }
    
    /**
     * Update branch information
     */
    public void updateBranchInfo(String branchCode, String region, BranchType branchType) {
        this.branchCode = trimOrNull(branchCode);
        this.region = trimOrNull(region);
        if (branchType != null) {
            this.branchType = branchType;
        }
        touch();
    }
    
    /**
     * Updates the company's basic information.
     * 
     * @param name the new company name
     * @param phone the new phone number
     * @param website the new website URL
     * @param activitySector the new activity sector
     */
    public void updateBasicInfo(String name, String phone, String website, String activitySector) {
        if (name != null && !name.trim().isEmpty()) {
            this.name = name.trim();
        }
        
        this.phone = trimOrNull(phone);
        this.website = trimOrNull(website);
        this.activitySector = trimOrNull(activitySector);
        
        touch();
    }
    
    /**
     * Updates the company's email address.
     * 
     * @param newEmail the new email address
     * @throws IllegalArgumentException if email is invalid
     */
    public void updateEmail(String newEmail) {
        validateNotEmpty(newEmail, "Email");
        this.email = new Email(newEmail);
        touch();
    }
    
    /**
     * Updates the company's CNPJ.
     * 
     * @param newCnpj the new CNPJ
     * @throws IllegalArgumentException if CNPJ is invalid
     */
    public void updateCnpj(String newCnpj) {
        if (newCnpj != null && !newCnpj.trim().isEmpty()) {
            this.cnpj = new Cnpj(newCnpj);
        } else {
            this.cnpj = null;
        }
        touch();
    }
    
    /**
     * Updates the company's address.
     * 
     * @param newAddress the new address
     */
    public void updateAddress(Address newAddress) {
        this.address = newAddress;
        touch();
    }
    
    /**
     * Updates the company's logo URL.
     * 
     * @param logoUrl the new logo URL
     */
    public void updateLogoUrl(String logoUrl) {
        this.logoUrl = trimOrNull(logoUrl);
        touch();
    }
    
    /**
     * Changes the company's subscription plan.
     * 
     * @param newPlan the new subscription plan
     * @throws IllegalArgumentException if the plan transition is not allowed
     */
    public void changePlan(CompanyPlan newPlan) {
        validateNotNull(newPlan, "New plan");
        
        if (this.activePlan == newPlan) {
            return; // No change needed
        }
        
        // Validate plan transition (business rule)
        if (!canUpgradeTo(newPlan) && !canDowngradeTo(newPlan)) {
            throw new IllegalArgumentException("Invalid plan transition from " + 
                                             activePlan + " to " + newPlan);
        }
        
        this.activePlan = newPlan;
        applyPlanLimits(newPlan);
        touch();
    }
    
    /**
     * Changes the company status.
     * 
     * @param newStatus the new status
     * @throws IllegalArgumentException if the status transition is not allowed
     */
    public void changeStatus(CompanyStatus newStatus) {
        validateNotNull(newStatus, "New status");
        
        if (!this.status.canTransitionTo(newStatus)) {
            throw new IllegalArgumentException("Cannot transition from " + 
                                             this.status + " to " + newStatus);
        }
        
        this.status = newStatus;
        touch();
    }
    
    /**
     * Activates the company.
     */
    public void activate() {
        changeStatus(CompanyStatus.ATIVO);
    }
    
    /**
     * Deactivates the company.
     */
    public void deactivate() {
        changeStatus(CompanyStatus.INATIVO);
    }
    
    /**
     * Suspends the company.
     */
    public void suspend() {
        changeStatus(CompanyStatus.SUSPENSO);
    }
    
    /**
     * Cancels the company subscription.
     */
    public void cancel() {
        changeStatus(CompanyStatus.CANCELADO);
        
        // Set expiration date to 30 days from now (grace period)
        this.expirationDate = Instant.now().plusSeconds(30L * 24 * 60 * 60); // 30 days
    }
    
    /**
     * Extends the company's subscription.
     * 
     * @param extensionMonths the number of months to extend
     */
    public void extendSubscription(int extensionMonths) {
        if (extensionMonths <= 0) {
            throw new IllegalArgumentException("Extension months must be positive");
        }
        
        Instant currentExpiration = this.expirationDate != null ? 
                                   this.expirationDate : Instant.now();
        
        this.expirationDate = currentExpiration.plusSeconds(
            extensionMonths * 30L * 24 * 60 * 60); // Approximate month calculation
        
        touch();
    }
    
    // Query methods
    
    /**
     * Checks if the company is active and operational.
     * 
     * @return true if the company is active
     */
    public boolean isActive() {
        return status == CompanyStatus.ATIVO && !isExpired();
    }
    
    /**
     * Checks if the company subscription has expired.
     * 
     * @return true if expired
     */
    public boolean isExpired() {
        return expirationDate != null && Instant.now().isAfter(expirationDate);
    }
    
    /**
     * Checks if the company is in a grace period after cancellation.
     * 
     * @return true if in grace period
     */
    public boolean isInGracePeriod() {
        return status == CompanyStatus.CANCELADO && !isExpired();
    }
    
    /**
     * Gets the number of days until expiration.
     * 
     * @return days until expiration, or -1 if no expiration date
     */
    public long getDaysUntilExpiration() {
        if (expirationDate == null) {
            return -1;
        }
        
        long diffInSeconds = expirationDate.getEpochSecond() - Instant.now().getEpochSecond();
        return Math.max(0, diffInSeconds / (24 * 60 * 60));
    }
    
    /**
     * Checks if the company can upgrade to the specified plan.
     * 
     * @param targetPlan the target plan
     * @return true if upgrade is possible
     */
    public boolean canUpgradeTo(CompanyPlan targetPlan) {
        return activePlan.canUpgradeTo(targetPlan);
    }
    
    /**
     * Checks if the company can downgrade to the specified plan.
     * 
     * @param targetPlan the target plan
     * @return true if downgrade is possible
     */
    public boolean canDowngradeTo(CompanyPlan targetPlan) {
        // Downgrades are generally allowed but may require validation
        return targetPlan.ordinal() < activePlan.ordinal();
    }
    
    /**
     * Checks if the company can create more users.
     * 
     * @param currentUserCount the current number of users
     * @return true if more users can be created
     */
    public boolean canCreateMoreUsers(int currentUserCount) {
        return currentUserCount < usersLimit;
    }
    
    /**
     * Checks if the company can add more social accounts.
     * 
     * @param currentAccountCount the current number of social accounts
     * @return true if more accounts can be added
     */
    public boolean canAddMoreSocialAccounts(int currentAccountCount) {
        return currentAccountCount < socialAccountsLimit;
    }
    
    /**
     * Checks if the company can create more posts this month.
     * 
     * @param currentMonthPostCount the current month's post count
     * @return true if more posts can be created
     */
    public boolean canCreateMorePosts(int currentMonthPostCount) {
        return currentMonthPostCount < monthlyPostsLimit;
    }
    
    // Private helper methods
    
    /**
     * Applies the limits associated with a subscription plan.
     * 
     * @param plan the subscription plan
     */
    private void applyPlanLimits(CompanyPlan plan) {
        this.socialAccountsLimit = plan.getSocialAccountsLimit();
        this.monthlyPostsLimit = plan.getMonthlyPostsLimit();
        this.usersLimit = plan.getUsersLimit();
    }
    
    /**
     * Trims a string and returns null if empty.
     * 
     * @param value the string to trim
     * @return trimmed string or null
     */
    private String trimOrNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
    
    /**
     * Validates that a string is not null or empty.
     * 
     * @param value the string to validate
     * @param fieldName the field name for error messages
     */
    private void validateNotEmpty(String value, String fieldName) {
        if (value == null || value.trim().isEmpty()) {
            throw new IllegalArgumentException(fieldName + " cannot be null or empty");
        }
    }
    
    /**
     * Validates that an object is not null.
     * 
     * @param value the object to validate
     * @param fieldName the field name for error messages
     */
    private void validateNotNull(Object value, String fieldName) {
        if (value == null) {
            throw new IllegalArgumentException(fieldName + " cannot be null");
        }
    }
    
    @Override
    public void validate() {
        super.validate();
        
        validateNotEmpty(name, "Company name");
        validateNotNull(email, "Company email");
        validateNotNull(activePlan, "Active plan");
        validateNotNull(status, "Company status");
        validateNotNull(branchType, "Branch type");
        validateNotNull(branchStatus, "Branch status");
        
        if (socialAccountsLimit <= 0) {
            throw new IllegalStateException("Social accounts limit must be positive");
        }
        
        if (monthlyPostsLimit <= 0) {
            throw new IllegalStateException("Monthly posts limit must be positive");
        }
        
        if (usersLimit <= 0) {
            throw new IllegalStateException("Users limit must be positive");
        }
        
        if (expirationDate != null && registrationDate != null && 
            expirationDate.isBefore(registrationDate)) {
            throw new IllegalStateException("Expiration date cannot be before registration date");
        }
        
        if (hierarchyLevel == null || hierarchyLevel < 0) {
            throw new IllegalStateException("Hierarchy level cannot be null or negative");
        }
        
        if (isFilial() && parentCompany == null) {
            throw new IllegalStateException("Filial companies must have a parent company");
        }
        
        if (isMatriz() && parentCompany != null) {
            throw new IllegalStateException("Matriz companies cannot have a parent company");
        }
    }
    
    // === HIERARCHY GETTERS AND SETTERS ===
    
    public Company getParentCompany() {
        return parentCompany;
    }
    
    public Integer getHierarchyLevel() {
        return hierarchyLevel;
    }
    
    public void setHierarchyLevel(Integer hierarchyLevel) {
        this.hierarchyLevel = hierarchyLevel;
    }
    
    public BranchType getBranchType() {
        return branchType;
    }
    
    public void setBranchType(BranchType branchType) {
        this.branchType = branchType;
    }
    
    public String getBranchCode() {
        return branchCode;
    }
    
    public void setBranchCode(String branchCode) {
        this.branchCode = branchCode;
    }
    
    public String getRegion() {
        return region;
    }
    
    public void setRegion(String region) {
        this.region = region;
    }
    
    public BranchStatus getBranchStatus() {
        return branchStatus;
    }
    
    public void setBranchStatus(BranchStatus branchStatus) {
        this.branchStatus = branchStatus;
    }
    
    // === ENUMS ===
    
    public enum BranchType {
        MATRIZ,           // Head office/Main company
        FILIAL,           // Branch office
        SUBSIDIARIA,      // Subsidiary
        FRANQUIA,         // Franchise
        REPRESENTACAO     // Representative office
    }
    
    public enum BranchStatus {
        ACTIVE,           // Active branch
        INACTIVE,         // Temporarily inactive
        SUSPENDED,        // Suspended operations
        CLOSED,           // Permanently closed
        PENDING           // Pending activation
    }
    
    @Override
    public String toString() {
        return "Company{" +
               "id=" + getId() +
               ", companyCode='" + companyCode + '\'' +
               ", name='" + name + '\'' +
               ", email=" + email +
               ", status=" + status +
               ", activePlan=" + activePlan +
               '}';
    }
}
