package net.siegeln.cameleer.saas.vendor;

import net.siegeln.cameleer.saas.audit.AuditAction;
import net.siegeln.cameleer.saas.audit.AuditService;
import net.siegeln.cameleer.saas.identity.LogtoConfig;
import net.siegeln.cameleer.saas.identity.LogtoManagementClient;
import net.siegeln.cameleer.saas.identity.ServerApiClient;
import net.siegeln.cameleer.saas.provisioning.ProvisioningProperties;
import net.siegeln.cameleer.saas.identity.ServerApiClient.ServerHealthResponse;
import net.siegeln.cameleer.saas.license.LicenseEntity;
import net.siegeln.cameleer.saas.license.LicenseService;
import net.siegeln.cameleer.saas.provisioning.ProvisionResult;
import net.siegeln.cameleer.saas.provisioning.ServerStatus;
import net.siegeln.cameleer.saas.provisioning.TenantProvisionRequest;
import net.siegeln.cameleer.saas.provisioning.TenantProvisioner;
import net.siegeln.cameleer.saas.tenant.TenantEntity;
import net.siegeln.cameleer.saas.tenant.TenantRepository;
import net.siegeln.cameleer.saas.tenant.TenantService;
import net.siegeln.cameleer.saas.tenant.TenantStatus;
import net.siegeln.cameleer.saas.tenant.dto.CreateTenantRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Service
public class VendorTenantService {

    private static final Logger log = LoggerFactory.getLogger(VendorTenantService.class);
    private static final Duration DEFAULT_LICENSE_VALIDITY = Duration.ofDays(365);

    private final TenantService tenantService;
    private final TenantRepository tenantRepository;
    private final LicenseService licenseService;
    private final TenantProvisioner tenantProvisioner;
    private final ServerApiClient serverApiClient;
    private final LogtoManagementClient logtoClient;
    private final LogtoConfig logtoConfig;
    private final AuditService auditService;
    private final ProvisioningProperties provisioningProps;

    public VendorTenantService(TenantService tenantService,
                               TenantRepository tenantRepository,
                               LicenseService licenseService,
                               TenantProvisioner tenantProvisioner,
                               ServerApiClient serverApiClient,
                               LogtoManagementClient logtoClient,
                               LogtoConfig logtoConfig,
                               AuditService auditService,
                               ProvisioningProperties provisioningProps) {
        this.tenantService = tenantService;
        this.tenantRepository = tenantRepository;
        this.licenseService = licenseService;
        this.tenantProvisioner = tenantProvisioner;
        this.serverApiClient = serverApiClient;
        this.logtoClient = logtoClient;
        this.logtoConfig = logtoConfig;
        this.auditService = auditService;
        this.provisioningProps = provisioningProps;
    }

    @Transactional
    public TenantEntity createAndProvision(CreateTenantRequest request, UUID actorId) {
        // 1. Create tenant record (sets status = PROVISIONING) + Logto org
        TenantEntity tenant = tenantService.create(request, actorId);

        // 2. Create initial admin user in Logto org (if credentials provided)
        if (tenant.getLogtoOrgId() != null && logtoClient.isAvailable()) {
            String ownerRoleId = logtoClient.findOrgRoleIdByName("owner");

            // Create tenant admin
            if (request.adminUsername() != null && request.adminPassword() != null) {
                try {
                    logtoClient.createUserWithPassword(
                        request.adminUsername(), request.adminPassword(),
                        tenant.getLogtoOrgId(), ownerRoleId);
                } catch (Exception e) {
                    log.warn("Failed to create admin user for tenant {}: {}", tenant.getSlug(), e.getMessage());
                }
            }

            // Add the current vendor user to the new org for support access
            try {
                String vendorUserId = actorId.toString();
                logtoClient.addUserToOrganization(tenant.getLogtoOrgId(), vendorUserId);
                if (ownerRoleId != null) {
                    logtoClient.assignOrganizationRole(tenant.getLogtoOrgId(), vendorUserId, ownerRoleId);
                }
            } catch (Exception e) {
                log.warn("Failed to add vendor to org for tenant {}: {}", tenant.getSlug(), e.getMessage());
            }

            // Register OIDC redirect URIs for the tenant's server in the Traditional Web App
            String tradAppId = logtoConfig.getTradAppId();
            if (tradAppId != null) {
                String base = provisioningProps.publicProtocol() + "://" + provisioningProps.publicHost();
                String slug = tenant.getSlug();
                logtoClient.addAppRedirectUris(tradAppId,
                    List.of(base + "/t/" + slug + "/oidc/callback"),
                    List.of(base + "/t/" + slug, base + "/t/" + slug + "/login?local"));
            }
        }

        // 3. Generate license
        LicenseEntity license = licenseService.generateLicense(tenant, DEFAULT_LICENSE_VALIDITY, actorId);

        // 4. Provision server if provisioner is available
        if (tenantProvisioner.isAvailable()) {
            var provisionRequest = new TenantProvisionRequest(
                    tenant.getId(), tenant.getSlug(),
                    tenant.getTier().name(), license.getToken());
            ProvisionResult result = tenantProvisioner.provision(provisionRequest);
            if (result.success()) {
                tenant.setServerEndpoint(result.serverEndpoint());
                tenant.setProvisionError(null);
                tenant.setStatus(TenantStatus.ACTIVE);
                tenant = tenantRepository.save(tenant);

                // Push license to newly provisioned server
                try {
                    serverApiClient.pushLicense(result.serverEndpoint(), license.getToken());
                } catch (Exception e) {
                    log.warn("License push failed for tenant {}: {}", tenant.getSlug(), e.getMessage());
                }
            } else {
                tenant.setProvisionError(result.error());
                tenant.setStatus(TenantStatus.PROVISIONING);
                tenant = tenantRepository.save(tenant);
                log.error("Provisioning failed for tenant {}: {}", tenant.getSlug(), result.error());
            }
        }

        auditService.log(actorId, null, tenant.getId(),
                AuditAction.TENANT_CREATE, "provision:" + tenant.getSlug(),
                null, null, "SUCCESS", null);

        return tenant;
    }

    public List<TenantEntity> listAll() {
        return tenantService.findAll().stream()
            .filter(t -> t.getStatus() != TenantStatus.DELETED)
            .toList();
    }

    public Optional<TenantEntity> getById(UUID id) {
        return tenantService.getById(id);
    }

    public Optional<LicenseEntity> getLicenseForTenant(UUID tenantId) {
        return licenseService.getActiveLicense(tenantId);
    }

    public ServerStatus getServerStatus(TenantEntity tenant) {
        if (!tenantProvisioner.isAvailable()) {
            return ServerStatus.notFound();
        }
        return tenantProvisioner.getStatus(tenant.getSlug());
    }

    public ServerHealthResponse getServerHealth(TenantEntity tenant) {
        String endpoint = tenant.getServerEndpoint();
        if (endpoint == null || endpoint.isBlank()) {
            return new ServerHealthResponse(false, "NO_ENDPOINT");
        }
        return serverApiClient.getHealth(endpoint);
    }

    @Transactional
    public TenantEntity suspend(UUID tenantId, UUID actorId) {
        TenantEntity tenant = tenantService.getById(tenantId)
                .orElseThrow(() -> new IllegalArgumentException("Tenant not found"));

        if (tenantProvisioner.isAvailable()) {
            try {
                tenantProvisioner.stop(tenant.getSlug());
            } catch (Exception e) {
                log.warn("Failed to stop containers for tenant {}: {}", tenant.getSlug(), e.getMessage());
            }
        }

        return tenantService.suspend(tenantId, actorId);
    }

    @Transactional
    public TenantEntity activate(UUID tenantId, UUID actorId) {
        TenantEntity tenant = tenantService.getById(tenantId)
                .orElseThrow(() -> new IllegalArgumentException("Tenant not found"));

        if (tenantProvisioner.isAvailable()) {
            try {
                tenantProvisioner.start(tenant.getSlug());
            } catch (Exception e) {
                log.warn("Failed to start containers for tenant {}: {}", tenant.getSlug(), e.getMessage());
            }
        }

        return tenantService.activate(tenantId, actorId);
    }

    @Transactional
    public void delete(UUID tenantId, UUID actorId) {
        TenantEntity tenant = tenantService.getById(tenantId)
                .orElseThrow(() -> new IllegalArgumentException("Tenant not found"));

        // Remove containers
        if (tenantProvisioner.isAvailable()) {
            try {
                tenantProvisioner.remove(tenant.getSlug());
            } catch (Exception e) {
                log.warn("Failed to remove containers for tenant {}: {}", tenant.getSlug(), e.getMessage());
            }
        }

        // Revoke license
        licenseService.revokeLicense(tenantId, actorId);

        // Delete Logto org
        if (logtoClient.isAvailable() && tenant.getLogtoOrgId() != null) {
            try {
                logtoClient.deleteOrganization(tenant.getLogtoOrgId());
            } catch (Exception e) {
                log.warn("Failed to delete Logto org for tenant {}: {}", tenant.getSlug(), e.getMessage());
            }
        }

        // Soft-delete
        tenant.setStatus(TenantStatus.DELETED);
        tenantRepository.save(tenant);

        auditService.log(actorId, null, tenantId,
                AuditAction.TENANT_DELETE, tenant.getSlug(),
                null, null, "SUCCESS", null);
    }

    @Transactional
    public LicenseEntity renewLicense(UUID tenantId, UUID actorId) {
        TenantEntity tenant = tenantService.getById(tenantId)
                .orElseThrow(() -> new IllegalArgumentException("Tenant not found"));

        // Revoke current license
        licenseService.revokeLicense(tenantId, actorId);

        // Generate new license
        LicenseEntity newLicense = licenseService.generateLicense(tenant, DEFAULT_LICENSE_VALIDITY, actorId);

        // Push to server
        String endpoint = tenant.getServerEndpoint();
        if (endpoint != null && !endpoint.isBlank()) {
            try {
                serverApiClient.pushLicense(endpoint, newLicense.getToken());
            } catch (Exception e) {
                log.warn("Failed to push renewed license to server for tenant {}: {}", tenant.getSlug(), e.getMessage());
            }
        }

        return newLicense;
    }
}
