/*
 * Copyright 2012 gitblit.com.
 *
 * 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 com.gitblit.authority;

import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.eclipse.jgit.lib.Config;

import com.gitblit.Constants;
import com.gitblit.models.UserModel;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.utils.TimeUtils;
import com.gitblit.utils.X509Utils.RevocationReason;

public class UserCertificateModel implements Comparable<UserCertificateModel> {
		public UserModel user;
		public Date expires;
		public List<X509Certificate> certs;
		public List<String> revoked;
		public String notes;

		public UserCertificateModel(UserModel user) {
			this.user = user;
		}
		
		public void update(Config config) {
			if (expires == null) {
				config.unset("user",  user.username, "expires");
			} else {
				SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);
				config.setString("user", user.username, "expires", df.format(expires));
			}
			if (StringUtils.isEmpty(notes)) {
				config.unset("user",  user.username, "notes");
			} else {
				config.setString("user", user.username, "notes", notes);
			}
			if (ArrayUtils.isEmpty(revoked)) {
				config.unset("user",  user.username, "revoked");
			} else {
				config.setStringList("user", user.username, "revoked", revoked);
			}
		}

		@Override
		public int compareTo(UserCertificateModel o) {
			return user.compareTo(o.user);
		}
		
		public void revoke(BigInteger serial, RevocationReason reason) {
			if (revoked == null) {
				revoked = new ArrayList<String>();
			}
			revoked.add(serial.toString() + ":" + reason.ordinal());
			expires = null;
			for (X509Certificate cert : certs) {
				if (!isRevoked(cert.getSerialNumber())) {
					if (!isExpired(cert.getNotAfter())) {
						if (expires == null || cert.getNotAfter().after(expires)) {
							expires = cert.getNotAfter();
						}
					}
				}
			}
		}
		
		public boolean isRevoked(BigInteger serial) {
			return isRevoked(serial.toString());
		}

		public boolean isRevoked(String serial) {
			if (ArrayUtils.isEmpty(revoked)) {
				return false;
			}
			String sn = serial + ":";
			for (String s : revoked) {
				if (s.startsWith(sn)) {
					return true;
				}
			}
			return false;
		}
		
		public RevocationReason getRevocationReason(BigInteger serial) {
			try {
				String sn = serial + ":";
				for (String s : revoked) {
					if (s.startsWith(sn)) {
						String r = s.substring(sn.length());
						int i = Integer.parseInt(r);
						return RevocationReason.values()[i];
					}
				}
			} catch (Exception e) {
			}
			return RevocationReason.unspecified;
		}
		
		public CertificateStatus getStatus() {
			if (expires == null) {
				return CertificateStatus.unknown;
			} else if (isExpired(expires)) {
				return CertificateStatus.expired;
			} else if (isExpiring(expires)) {
				return CertificateStatus.expiring;
			}
			return CertificateStatus.ok;
		}

		public boolean hasExpired() {
			return expires != null && isExpiring(expires);
		}

		public CertificateStatus getStatus(X509Certificate cert) {
			if (isRevoked(cert.getSerialNumber())) {
				return CertificateStatus.revoked;
			} else if (isExpired(cert.getNotAfter())) {
				return CertificateStatus.expired;
			} else if (isExpiring(cert.getNotAfter())) {
				return CertificateStatus.expiring;
			}
			return CertificateStatus.ok;
		}
		
		private boolean isExpiring(Date date) {
			return (date.getTime() - System.currentTimeMillis()) <= TimeUtils.ONEDAY * 30;
		}
		
		private boolean isExpired(Date date) {
			return date.getTime() < System.currentTimeMillis();
		}
	}