// Copyright 2025.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ssh_config_file

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/Adembc/lazyssh/internal/core/domain"
	"go.uber.org/zap"
)

type ServerMetadata struct {
	Tags     []string `json:"tags,omitempty"`
	LastSeen string   `json:"last_seen,omitempty"`
	PinnedAt string   `json:"pinned_at,omitempty"`
	SSHCount int      `json:"ssh_count,omitempty"`
}

type metadataManager struct {
	filePath string
	logger   *zap.SugaredLogger
}

func newMetadataManager(filePath string, logger *zap.SugaredLogger) *metadataManager {
	return &metadataManager{filePath: filePath, logger: logger}
}

func (m *metadataManager) loadAll() (map[string]ServerMetadata, error) {
	metadata := make(map[string]ServerMetadata)

	if _, err := os.Stat(m.filePath); os.IsNotExist(err) {
		return metadata, nil
	}

	data, err := os.ReadFile(m.filePath)
	if err != nil {
		return nil, fmt.Errorf("read metadata '%s': %w", m.filePath, err)
	}

	if len(data) == 0 {
		return metadata, nil
	}

	if err := json.Unmarshal(data, &metadata); err != nil {
		return nil, fmt.Errorf("parse metadata JSON '%s': %w", m.filePath, err)
	}

	return metadata, nil
}

func (m *metadataManager) saveAll(metadata map[string]ServerMetadata) error {
	if err := m.ensureDirectory(); err != nil {
		m.logger.Errorw("failed to ensure metadata directory", "path", m.filePath, "error", err)

		return fmt.Errorf("ensure metadata directory for '%s': %w", m.filePath, err)
	}

	data, err := json.MarshalIndent(metadata, "", "  ")
	if err != nil {
		m.logger.Errorw("failed to marshal metadata", "path", m.filePath, "error", err)
		return fmt.Errorf("marshal metadata for '%s': %w", m.filePath, err)
	}

	if err := os.WriteFile(m.filePath, data, 0o600); err != nil {
		m.logger.Errorw("failed to write metadata file", "path", m.filePath, "error", err)
		return fmt.Errorf("write metadata '%s': %w", m.filePath, err)
	}
	return nil
}

func (m *metadataManager) updateServer(server domain.Server, oldAlias string) error {
	metadata, err := m.loadAll()
	if err != nil {
		m.logger.Errorw("failed to load metadata in updateServer", "path", m.filePath, "alias", server.Alias, "old_alias", oldAlias, "error", err)
		return fmt.Errorf("load metadata: %w", err)
	}

	if oldAlias != server.Alias {
		oldMeta, ok := metadata[oldAlias]
		if ok {
			metadata[server.Alias] = oldMeta
		}
		delete(metadata, oldAlias)
	}

	existing := metadata[server.Alias]
	merged := existing

	merged.Tags = server.Tags

	if !server.LastSeen.IsZero() {
		merged.LastSeen = server.LastSeen.Format(time.RFC3339)
	}

	if !server.PinnedAt.IsZero() {
		merged.PinnedAt = server.PinnedAt.Format(time.RFC3339)
	}

	if server.SSHCount > 0 {
		merged.SSHCount = server.SSHCount
	}

	metadata[server.Alias] = merged
	return m.saveAll(metadata)
}

func (m *metadataManager) deleteServer(alias string) error {
	metadata, err := m.loadAll()
	if err != nil {
		m.logger.Errorw("failed to load metadata in deleteServer", "path", m.filePath, "alias", alias, "error", err)
		return fmt.Errorf("load metadata: %w", err)
	}

	delete(metadata, alias)
	return m.saveAll(metadata)
}

func (m *metadataManager) setPinned(alias string, pinned bool) error {
	metadata, err := m.loadAll()
	if err != nil {
		m.logger.Errorw("failed to load metadata in setPinned", "path", m.filePath, "alias", alias, "pinned", pinned, "error", err)
		return fmt.Errorf("load metadata: %w", err)
	}

	meta := metadata[alias]
	if pinned {
		meta.PinnedAt = time.Now().Format(time.RFC3339)
	} else {
		meta.PinnedAt = ""
	}

	metadata[alias] = meta
	return m.saveAll(metadata)
}

func (m *metadataManager) recordSSH(alias string) error {
	metadata, err := m.loadAll()
	if err != nil {
		m.logger.Errorw("failed to load metadata in recordSSH", "path", m.filePath, "alias", alias, "error", err)
		return fmt.Errorf("load metadata: %w", err)
	}

	meta := metadata[alias]
	meta.LastSeen = time.Now().Format(time.RFC3339)
	meta.SSHCount++

	metadata[alias] = meta
	return m.saveAll(metadata)
}

func (m *metadataManager) ensureDirectory() error {
	dir := filepath.Dir(m.filePath)
	if err := os.MkdirAll(dir, 0o750); err != nil {
		return fmt.Errorf("mkdir '%s': %w", dir, err)
	}
	return nil
}
